pi-link 0.1.16 → 0.1.17

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 CHANGED
@@ -6,6 +6,25 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
6
6
 
7
7
  ---
8
8
 
9
+ ## 0.1.17 — 2026-07-01
10
+
11
+ ### Changed
12
+
13
+ - **`link_send` now rejects self-targets.** Sending to your own link name returns an immediate `self_target` error instead of delivering the message back to yourself, matching the existing `link_prompt` and `link_compact` behavior.
14
+
15
+ ### Fixed
16
+
17
+ - **Disconnected link tool calls are now marked as errors.** `notConnectedResult()` tags `error: "not_connected"`, so callers and renderers treat a disconnected call as a failure instead of a success-looking result.
18
+ - **Hub ignores duplicate `register` messages.** A second `register` on an already-registered client socket is now dropped instead of re-running the join flow (which could reassign the name and emit a spurious re-join).
19
+ - **`link_prompt` and `link_compact` honor an already-aborted signal.** Both tools now check `signal.aborted` at the top of `execute` and return an `error: "aborted"` result immediately, rather than starting work that was cancelled before it began.
20
+
21
+ ### Internal
22
+
23
+ - Removed the unused `ExtensionContext` parameter from `shouldConnect()`.
24
+ - Clarified the keepalive comment: it presumes `sendUserMessage()` starts a run, with the sender's 30-minute hard ceiling as the backstop if it ever doesn't.
25
+
26
+ ---
27
+
9
28
  ## 0.1.16 — 2026-06-09
10
29
 
11
30
  ### Added
package/README.md CHANGED
@@ -255,7 +255,7 @@ Note: `triggerTurn` does **not** cause the response to come back to the caller -
255
255
 
256
256
  > **Broadcast note:** Sending to `"*"` delivers to **all other terminals** - the sender is excluded.
257
257
 
258
- Pre-validates the target name against the local terminal list before sending, catching typos early. See [Message Routing](#message-routing--error-handling) for delivery semantics.
258
+ Pre-validates the target name against the local terminal list before sending, catching typos early. See [Message Routing](#message-routing--error-handling) for delivery semantics. **Self-target rejection** - sending to yourself (`to` equals your own name) returns an immediate error.
259
259
 
260
260
  ### `link_prompt`
261
261
 
package/index.ts CHANGED
@@ -433,7 +433,7 @@ export default function (pi: ExtensionAPI) {
433
433
 
434
434
  // ── Connection intent ──────────────────────────────────────────────────
435
435
 
436
- function shouldConnect(_ctx: ExtensionContext): boolean {
436
+ function shouldConnect(): boolean {
437
437
  const data = latestCustomData("link-active") as
438
438
  | { active?: boolean }
439
439
  | undefined;
@@ -775,7 +775,9 @@ export default function (pi: ExtensionAPI) {
775
775
  });
776
776
  } else {
777
777
  pendingRemotePrompt = { id: msg.id, from: msg.from };
778
- // Keepalive: periodic status push so sender knows we're alive
778
+ // Keepalive: periodic status push so sender knows we're alive.
779
+ // Keepalive presumes sendUserMessage() starts a run (platform contract);
780
+ // if it ever doesn't, the sender's 30 min hard ceiling is the backstop.
779
781
  if (keepaliveTimer) clearInterval(keepaliveTimer);
780
782
  keepaliveTimer = setInterval(
781
783
  () => pushStatus(true),
@@ -848,6 +850,7 @@ export default function (pi: ExtensionAPI) {
848
850
 
849
851
  // First message must be register
850
852
  if (msg.type === "register") {
853
+ if (clientName) return; // already registered — ignore duplicate
851
854
  clientName = uniqueName(msg.name);
852
855
  hubClients.set(clientWs, clientName);
853
856
  if (msg.cwd) hubTerminalCwds.set(clientName, msg.cwd);
@@ -1237,7 +1240,7 @@ export default function (pi: ExtensionAPI) {
1237
1240
  }
1238
1241
  }
1239
1242
 
1240
- if (flagName || shouldConnect(_ctx)) scheduleStartupConnect();
1243
+ if (flagName || shouldConnect()) scheduleStartupConnect();
1241
1244
  });
1242
1245
 
1243
1246
  pi.on("session_shutdown", async () => {
@@ -1317,7 +1320,7 @@ export default function (pi: ExtensionAPI) {
1317
1320
  }
1318
1321
 
1319
1322
  function notConnectedResult() {
1320
- return textResult("Not connected to link");
1323
+ return textResult("Not connected to link", { error: "not_connected" });
1321
1324
  }
1322
1325
 
1323
1326
  function truncatePreview(text: string, max = 60) {
@@ -1377,6 +1380,12 @@ export default function (pi: ExtensionAPI) {
1377
1380
 
1378
1381
  // Pre-validate target exists locally (best-effort, catches typos and definitely-absent names)
1379
1382
  if (params.to !== "*") {
1383
+ if (params.to === terminalName) {
1384
+ return textResult("Cannot send to yourself", {
1385
+ to: params.to,
1386
+ error: "self_target",
1387
+ });
1388
+ }
1380
1389
  const miss = targetNotFound(params.to);
1381
1390
  if (miss) return miss;
1382
1391
  }
@@ -1439,6 +1448,13 @@ export default function (pi: ExtensionAPI) {
1439
1448
  }),
1440
1449
 
1441
1450
  async execute(_toolCallId, params, signal) {
1451
+ if (signal?.aborted) {
1452
+ return textResult("Compact request aborted", {
1453
+ to: params.to,
1454
+ error: "aborted",
1455
+ });
1456
+ }
1457
+
1442
1458
  if (role === "disconnected") return notConnectedResult();
1443
1459
 
1444
1460
  if (params.to === terminalName) {
@@ -1537,6 +1553,13 @@ export default function (pi: ExtensionAPI) {
1537
1553
  }),
1538
1554
 
1539
1555
  async execute(_toolCallId, params, signal) {
1556
+ if (signal?.aborted) {
1557
+ return textResult("Prompt request aborted", {
1558
+ to: params.to,
1559
+ error: "aborted",
1560
+ });
1561
+ }
1562
+
1540
1563
  if (role === "disconnected") return notConnectedResult();
1541
1564
 
1542
1565
  if (params.to === terminalName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-link",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "WebSocket-based inter-terminal communication for Pi. Connect multiple Pi terminals over a local link network.",
5
5
  "author": "alvivar",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@ Synchronous RPC. Send a prompt, wait for the response.
48
48
 
49
49
  ### `link_send`
50
50
 
51
- Fire-and-forget. Send to one terminal or `to: "*"` to broadcast to every other connected terminal; there is no exclusion filter.
51
+ Fire-and-forget. Send to one terminal or `to: "*"` to broadcast to every other connected terminal; there is no exclusion filter. Sending to yourself is rejected. A send to a name that isn't connected fails with an error listing who _is_ connected — delivery failure is visible to the sender, not silent.
52
52
 
53
53
  Set `triggerTurn: true` to queue async work on the receiver. The sender does **not** get an automatic response back.
54
54
 
@@ -69,7 +69,7 @@ Use `triggerTurn: false` for fire-and-forget status notifications only — when
69
69
 
70
70
  ### `link_compact`
71
71
 
72
- Blocks until the target finishes compacting, then returns. Ask another terminal to compact its context window into a summary, freeing space. `link_list` exposes context usage; `link_compact` is the lever when you decide a terminal should free context — and because the call only returns once compaction is done, you can immediately hand it more work with `link_send`/`link_prompt` (no sleep, no busy-bounce). Busy targets (mid-turn or already compacting) decline; retry when `link_list` shows them idle. You decide the threshold; pi-link just runs the compaction you ask for. Its optional `instructions` add an extra focus to that summary.
72
+ Blocks until the target finishes compacting, then returns (3 min ceiling — the call then errors for you, though the target may still finish compacting on its own; check it before assuming failure). Ask another terminal to compact its context window into a summary, freeing space. `link_list` exposes context usage; `link_compact` is the lever when you decide a terminal should free context — and because the call only returns once compaction is done, you can immediately hand it more work with `link_send`/`link_prompt` (no sleep, no busy-bounce). Busy targets (mid-turn or already compacting) decline; retry when `link_list` shows them idle. You decide the threshold; pi-link just runs the compaction you ask for. Its optional `instructions` add an extra focus to that summary.
73
73
 
74
74
  ---
75
75