pi-link 0.4.0 → 0.4.1
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/CHANGELOG.md +14 -0
- package/README.md +6 -0
- package/index.ts +87 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.4.1 — 2026-09-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Received link messages now sit in Pi's own message panel.** A delivery is drawn in the same background panel Pi gives extension messages, indented by your configured output padding, so link traffic lines up with the rest of the transcript instead of standing outside it.
|
|
14
|
+
|
|
15
|
+
- **A long delivery is collapsed to a preview.** It shows the first six content rows as they wrap at the available content width inside the panel, then `... (N more lines, ctrl+o to expand)`; a message that already fits shows whole, with no hint. The six counts content rows only — the panel's padding and the hint itself sit outside it, so the panel has no fixed height. Expanding is Pi's existing global toggle, `Ctrl+O` unless you have rebound `app.tools.expand`, and the hint names your actual binding when one is assigned. It expands every custom message at once, including ones restored from an earlier session; pi-link adds no toggle of its own. When the expansion shortcut is unbound, incoming and outgoing previews show `bind app.tools.expand to expand`. Only the display changes — the full text is still what reaches the model, what was delivered, and what the session file keeps.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Outgoing `link_send` and `link_compact` calls now follow Pi's global expansion toggle.** Collapsed, the message — or the compaction instructions — has runs of whitespace collapsed to single spaces and keeps the first 60 characters of that result; on a narrow terminal that preview may still wrap onto more than one row. Expanded, the uncollapsed text is shown with its line breaks and spacing as rendered by Pi. Indentation of the surrounding call is unchanged, and the tool's own result line still reports send or compaction status — never the other terminal's reply.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
9
23
|
## 0.4.0 — 2026-09-04
|
|
10
24
|
|
|
11
25
|
### Added
|
package/README.md
CHANGED
|
@@ -717,3 +717,9 @@ Because a gated flush does not reschedule, the release path is load-bearing. `re
|
|
|
717
717
|
### Rendering
|
|
718
718
|
|
|
719
719
|
Delivered link batches render with a styled `⚡ [link]` prefix using the theme's accent color; sender attribution lives in the `From "name":` blocks inside the message body, not in the prefix. The link status text in Pi's footer uses `theme.fg("dim", ...)` to match Pi's standard footer styling.
|
|
720
|
+
|
|
721
|
+
Deliveries sit in the same background panel Pi draws around extension messages by default, indented by your configured output padding, so link messages line up with the rest of the transcript instead of standing outside it.
|
|
722
|
+
|
|
723
|
+
A long delivery is previewed rather than printed whole. Collapsed, it shows the first six rows as they wrap at the current window width, followed by `... (N more lines, ctrl+o to expand)`; a message that already fits in six rows shows in full with no hint. Expanding is Pi's own global toggle — `Ctrl+O` unless you have rebound `app.tools.expand`, and the hint always names your actual binding — so it expands every custom message at once, including messages restored from an earlier session. If you have unbound `app.tools.expand` entirely there is no key to name, so both hints read `bind app.tools.expand to expand` instead. Only the display changes: the full text is what reaches the model, what `link_send` delivered, and what the session file keeps.
|
|
724
|
+
|
|
725
|
+
The same toggle also expands outgoing calls: `link_send` and `link_compact` collapse the message or instructions into a one-line preview — runs of whitespace become single spaces — and keep its first 60 characters, which a narrow terminal may still wrap onto more than one row. Expanding reveals the uncollapsed text with its line breaks and spacing as rendered by Pi. Expanding a call says nothing about delivery — the tool's own result line reports that.
|
package/index.ts
CHANGED
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
VERSION as PI_VERSION,
|
|
15
|
+
keyHint,
|
|
16
|
+
keyText,
|
|
15
17
|
type ExtensionAPI,
|
|
16
18
|
type ExtensionContext,
|
|
17
19
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
20
|
+
import { Box, Text, type Component } from "@earendil-works/pi-tui";
|
|
19
21
|
import { Type } from "typebox";
|
|
20
22
|
import * as crypto from "node:crypto";
|
|
21
23
|
import { createServer, type Server as HttpServer } from "node:http";
|
|
@@ -40,6 +42,8 @@ const CONNECT_HANDSHAKE_TIMEOUT_MS = 5_000;
|
|
|
40
42
|
const FLUSH_DELAY_MS = 200;
|
|
41
43
|
const BATCH_MAX_ITEMS = 20;
|
|
42
44
|
const BATCH_MAX_CHARS = 16_000;
|
|
45
|
+
// Visual rows of an incoming message kept while Pi's global expansion is off.
|
|
46
|
+
const PREVIEW_ROWS = 6;
|
|
43
47
|
|
|
44
48
|
// ─── Protocol ────────────────────────────────────────────────────────────────
|
|
45
49
|
|
|
@@ -151,6 +155,49 @@ function piVersionSupported(version: string): boolean {
|
|
|
151
155
|
return prerelease === undefined; // exactly the floor: only the release qualifies
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
// keyText is empty when app.tools.expand has no binding, and the native hint would then
|
|
159
|
+
// read " to expand" with no key in it, naming nothing the reader can press.
|
|
160
|
+
function expansionHint(dim: (text: string) => string): string {
|
|
161
|
+
return keyText("app.tools.expand")
|
|
162
|
+
? keyHint("app.tools.expand", "to expand")
|
|
163
|
+
: dim("bind app.tools.expand to expand");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The first PREVIEW_ROWS rows of `content`, plus a hint when rows were dropped.
|
|
168
|
+
*
|
|
169
|
+
* The rows come from rendering `content` at the real width, not from splitting the
|
|
170
|
+
* raw string on newlines: one long logical line wraps into many visual rows, and
|
|
171
|
+
* the same message yields different rows in a narrow window. Rendering is Text's
|
|
172
|
+
* job here, cached by (text, width), so the preview stays a slice of the identical
|
|
173
|
+
* output the expanded view shows.
|
|
174
|
+
*/
|
|
175
|
+
function messagePreview(
|
|
176
|
+
content: Text,
|
|
177
|
+
dim: (text: string) => string,
|
|
178
|
+
): Component {
|
|
179
|
+
const hint = new Text("", 0, 0);
|
|
180
|
+
return {
|
|
181
|
+
render(width: number): string[] {
|
|
182
|
+
const rows = content.render(width);
|
|
183
|
+
if (rows.length <= PREVIEW_ROWS) return rows;
|
|
184
|
+
// Rebuilt every render so a rebound key is never shown stale; it is one short
|
|
185
|
+
// line, and Text still caches the content rows, which are the expensive part.
|
|
186
|
+
hint.setText(
|
|
187
|
+
dim(`... (${rows.length - PREVIEW_ROWS} more lines, `) +
|
|
188
|
+
expansionHint(dim) +
|
|
189
|
+
dim(")"),
|
|
190
|
+
);
|
|
191
|
+
// The budget covers content only, so the hint may wrap rather than be cut off.
|
|
192
|
+
return [...rows.slice(0, PREVIEW_ROWS), ...hint.render(width)];
|
|
193
|
+
},
|
|
194
|
+
invalidate(): void {
|
|
195
|
+
content.invalidate();
|
|
196
|
+
hint.invalidate();
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
154
201
|
// ─── Extension ───────────────────────────────────────────────────────────────
|
|
155
202
|
|
|
156
203
|
export default function (pi: ExtensionAPI) {
|
|
@@ -1490,8 +1537,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
1490
1537
|
return textResult("Not connected to link", { error: "not_connected" });
|
|
1491
1538
|
}
|
|
1492
1539
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1540
|
+
// Collapsed one-line preview for the two outgoing tools. Whitespace is normalized so a
|
|
1541
|
+
// multiline message stays on the tool's single preview line, then clipped to 60
|
|
1542
|
+
// characters. The hint appears only when characters are actually hidden: expanding shows
|
|
1543
|
+
// the original text, so collapsing whitespace alone is not something to advertise.
|
|
1544
|
+
// Styling is applied per segment, like messagePreview above: the hint ends with a
|
|
1545
|
+
// foreground reset, so punctuation appended after it would render undimmed.
|
|
1546
|
+
function truncatePreview(text: string, dim: (text: string) => string) {
|
|
1547
|
+
const preview = text.replace(/\s+/g, " ");
|
|
1548
|
+
return preview.length > 60
|
|
1549
|
+
? dim(preview.slice(0, 60) + "... (") +
|
|
1550
|
+
expansionHint(dim) +
|
|
1551
|
+
dim(")")
|
|
1552
|
+
: dim(preview);
|
|
1495
1553
|
}
|
|
1496
1554
|
|
|
1497
1555
|
// Shared "target not found" result for the send/compact tools.
|
|
@@ -1566,16 +1624,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
1566
1624
|
return textResult(`${verb} ${target}`, { to: params.to });
|
|
1567
1625
|
},
|
|
1568
1626
|
|
|
1569
|
-
renderCall(args, theme) {
|
|
1627
|
+
renderCall(args, theme, context) {
|
|
1628
|
+
const dim = (text: string) => theme.fg("dim", text);
|
|
1570
1629
|
const preview =
|
|
1571
1630
|
typeof args.message === "string"
|
|
1572
|
-
?
|
|
1573
|
-
|
|
1631
|
+
? context.expanded
|
|
1632
|
+
? dim(args.message)
|
|
1633
|
+
: truncatePreview(args.message, dim)
|
|
1634
|
+
: dim("...");
|
|
1574
1635
|
const text =
|
|
1575
1636
|
theme.fg("toolTitle", theme.bold("link_send ")) +
|
|
1576
1637
|
theme.fg("accent", args.to) +
|
|
1577
1638
|
"\n " +
|
|
1578
|
-
|
|
1639
|
+
preview;
|
|
1579
1640
|
return new Text(text, 0, 0);
|
|
1580
1641
|
},
|
|
1581
1642
|
|
|
@@ -1679,11 +1740,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1679
1740
|
});
|
|
1680
1741
|
},
|
|
1681
1742
|
|
|
1682
|
-
renderCall(args, theme) {
|
|
1743
|
+
renderCall(args, theme, context) {
|
|
1683
1744
|
let text = theme.fg("toolTitle", theme.bold("link_compact "));
|
|
1684
1745
|
text += theme.fg("accent", String(args.to));
|
|
1746
|
+
const dim = (t: string) => theme.fg("dim", t);
|
|
1685
1747
|
if (typeof args.instructions === "string")
|
|
1686
|
-
text +=
|
|
1748
|
+
text +=
|
|
1749
|
+
"\n " +
|
|
1750
|
+
(context.expanded
|
|
1751
|
+
? dim(args.instructions)
|
|
1752
|
+
: truncatePreview(args.instructions, dim));
|
|
1687
1753
|
return new Text(text, 0, 0);
|
|
1688
1754
|
},
|
|
1689
1755
|
|
|
@@ -1915,12 +1981,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
1915
1981
|
|
|
1916
1982
|
// ── Message renderer ─────────────────────────────────────────────────────
|
|
1917
1983
|
|
|
1918
|
-
pi.registerMessageRenderer("link", (message,
|
|
1984
|
+
pi.registerMessageRenderer("link", (message, options, theme) => {
|
|
1919
1985
|
const from =
|
|
1920
1986
|
(message.details as Record<string, unknown> | undefined)?.from ?? "link";
|
|
1921
1987
|
const text =
|
|
1922
1988
|
theme.fg("accent", `⚡ [${from}] `) +
|
|
1923
1989
|
theme.fg("text", String(message.content));
|
|
1924
|
-
|
|
1990
|
+
const content = new Text(text, 0, 0);
|
|
1991
|
+
// The same panel Pi draws around extension messages by default: returning our own
|
|
1992
|
+
// component bypasses the host's box, so a link message would otherwise sit unframed
|
|
1993
|
+
// among framed ones. `outputPad` is the user's configured output padding.
|
|
1994
|
+
const box = new Box(options.outputPad, 1, (t) => theme.bg("customMessageBg", t));
|
|
1995
|
+
box.addChild(
|
|
1996
|
+
options.expanded
|
|
1997
|
+
? content
|
|
1998
|
+
: messagePreview(content, (s) => theme.fg("muted", s)),
|
|
1999
|
+
);
|
|
2000
|
+
return box;
|
|
1925
2001
|
});
|
|
1926
2002
|
}
|
package/package.json
CHANGED