pi-link 0.2.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,70 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
6
6
 
7
7
  ---
8
8
 
9
+ ## 0.4.0 — 2026-09-04
10
+
11
+ ### Added
12
+
13
+ - **The hub answers `GET /status` on the port it already owns.** A read-only JSON snapshot of who is connected right now, served over plain HTTP on `127.0.0.1:9900` alongside the WebSocket surface — no authentication, the same trust boundary as the link itself. The hub is listed first, then clients sorted by name. `status` and `sinceSeconds` are one optional pair, present together or absent together: absent means the hub has registered that terminal but not yet heard from it, which is unknown rather than idle. `context` is always present, `null` when there is no snapshot. Every other method and path answers 404. Reading the endpoint registers nothing, broadcasts nothing and changes no hub state, so watching the link no longer disturbs it — previously the only way to see live membership from outside Pi was to open a WebSocket and register, which announced a phantom terminal to the whole fleet.
14
+
15
+ - **`pi-link --status [--json]` reads that endpoint from the shell.** A table by default; `--json` writes the hub's response body verbatim for scripting. Three exit codes, with the two failures deliberately distinct so automation can tell an outage from a version mismatch without parsing anything: `0` on a valid payload, `2` with `No link hub running on :<port>.` when nothing answers, and `1` with `Link hub does not support /status — update pi-link and restart terminals.` when something answers that is not a hub speaking this contract. Every timeout is exit `2`, including a listener that returns headers and then stalls. A 0.3.0 hub answers plain HTTP with `426 Upgrade Required`, so an out-of-date fleet lands on exit `1` deterministically instead of looking like a dead link. `--status` and `--json` belong to the wrapper only until a session name appears; after one they forward to pi untouched, like any other passthrough flag.
16
+
17
+ ### Changed
18
+
19
+ - **The hub owns the HTTP server its WebSocket server rides on.** Serving `/status` means handing `ws` a server instead of letting it build one, and `ws` never closes a server it was handed. That server is therefore closed at every teardown site — a cancelled connection attempt, a `/link-disconnect` from an established hub, and a listener that arrives after its attempt was cancelled. A missed close would leave a process squatting `:9900` with no hub behind it, and no other terminal on the machine could become the hub. Election is unchanged: `ws` forwards `listening` and `error` from the provided server, so port-in-use still falls back to the client role exactly as before.
20
+
21
+ ### Compatibility
22
+
23
+ - **Two things in the `/status` payload may grow, and consumers must tolerate both.** Unknown *fields* may be added, so ignore what you do not recognize rather than rejecting the response. Unknown *`status` values* may appear too — the vocabulary is not frozen, and it has grown before, gaining `compacting` in 0.3.0 — so treat any non-empty string as possible. `pi-link --status` renders an unrecognized status as-is instead of refusing the payload.
24
+
25
+ - **Upgrading is not enough on its own: restart the terminals.** A running terminal keeps the hub it started with, so a 0.3.0 hub keeps answering `426` until the terminal hosting it restarts. Until then `pi-link --status` reports that the hub does not support `/status`, which is accurate — the fix is a restart, not a reinstall.
26
+
27
+ - **`PI_LINK_PORT` changes only where the CLI looks.** The extension always binds the hub to `9900`. The variable exists so tests can run a stub hub on a free port, and it is not validated: an unusable value simply fails the request and is reported back in the exit-`2` message.
28
+
29
+ ---
30
+
31
+ ## 0.3.0 — 2026-08-28
32
+
33
+ ### Added
34
+
35
+ - **A terminal now reports `compacting` while its link delivery gate is raised.** It previously reported `idle`, so `link_list` and `/link` showed it as free at the one moment it was least available: messages sent to it wait in its inbox and `link_compact` declines. One local predicate decides both the `compacting` status a terminal derives and whether its inbox delivery is held, and it takes precedence over every other status a terminal derives for itself. It means the gate is raised, not that a compaction is provably still running — a cancelled manual compaction leaves it raised until the next successful compaction, the terminal's next agent start, or a 180-second deadline. Automatic threshold and overflow compaction are not gated by pi-link and never report `compacting`; they run inside an agent run Pi has not settled yet, so a terminal running one reports `thinking`.
36
+
37
+ ### Breaking
38
+
39
+ - **`link_prompt` is removed.** The blocking prompt-and-wait tool is gone end to end: the tool, the `prompt_request` / `prompt_response` wire messages, the pending-response state, and its timeouts. `link_send` is not a replacement for it — sending is asynchronous and un-correlated. A send reports only its immediate send attempt, not what the target did with it. On a client, success means the message was handed to the hub connection; it does not confirm hub routing or receiver delivery. Nothing ties a later reply back to the call that prompted it. A worker that would have answered a prompt sends its answer with a later `link_send`, which is how callback-style coordination now works. Anything that needs a strict child operation with a join — one call that blocks until its own result comes back — needs orchestration owned by the host application.
40
+
41
+ - **`link_send` no longer takes a `triggerTurn` parameter.** Delivery behavior is the receiver's alone: a running receiver is steered at its next safe boundary, an idle one starts a turn. Which of the two happens is decided when the debounced batch is actually delivered, not when the message was sent, so it is not something a sender could have chosen correctly anyway. The tool takes `to` and `message` and nothing else.
42
+
43
+ - **Broadcast is removed.** `link_send` no longer treats `to: "*"` as a broadcast target, the hub no longer has wildcard routing, and `/link-broadcast` is gone. Each send routes to exactly one named recipient. Removing it from the tool alone would have left the feature reachable from the wire, so both went.
44
+
45
+ - **pi-link 0.3 requires Pi 0.84.2 or later.** The status lifecycle and the remote-compaction guard are built on Pi's `agent_settled` event and `ctx.isIdle()`, with one code path and no compatibility fallback. Pi's package installation does not check the host version, so `pi install` succeeds on an older Pi; pi-link then refuses to initialize rather than half-run. It throws before registering a single flag, event, tool or command and before opening any socket, and Pi reports the refusal with the required minimum and the detected version. Malformed versions and prereleases below the floor are refused the same way. On Pi 0.74–0.84.1 pin `pi-link@0.2.x`; on Pi 0.73 or earlier, `pi-link@0.1.14`.
46
+
47
+ - **Upgrade and restart every linked terminal together.** These removals changed the wire messages, and the link protocol carries no version of its own — nothing detects a mismatch or warns about one. One asymmetry has been observed directly: a new sender delivering to a 0.2.0 receiver can arrive as bare text, losing the `[Link: … message(s) received]` header and the `From "name":` line, with nothing reporting a fault. Restarting matters as much as upgrading, since a terminal keeps running the build it started with.
48
+
49
+ ### Changed
50
+
51
+ - **`link_compact` now describes the guard it actually applies.** A target accepts only when Pi reports its session idle and no manual compaction holds its gate; everything else declines as `busy`, and a runtime with no compaction capability still declines as `unsupported`. The 180-second timeout bounds the caller's wait only: once the request has been dispatched, a caller timeout or abort ends that wait and nothing else, and the target may still be compacting. A call aborted before it dispatches returns without having asked for anything. The tool no longer tells the caller to retry or to re-check; it reports what happened and leaves the next move to the caller.
52
+
53
+ - **The bundled `pi-link-coordination` skill now focuses on how the tools behave.** It explains when messages start or steer turns, how status, callbacks and remote compaction behave, and what terminal names, working directories and mixed versions mean. Advice for the removed `link_prompt`, sender-selected delivery, broadcast and workflow/retry conventions is gone.
54
+
55
+ ### Fixed
56
+
57
+ - **Once Pi announces a manual compaction, link messages wait instead of starting a turn against context being rebuilt.** Pi refuses an ordinary prompt during a manual compaction, but pi-link's delivery path is not an ordinary prompt and that refusal does not cover it. While the gate is raised the inbox holds messages, and the gate releases them when Pi reports the compaction succeeded, on the terminal's next agent start, or on a deadline if neither arrives. Coverage begins at the announcement: a human `/compact` aborts and prepares before Pi announces it, so a delivery landing in that short window is still possible. The message itself is never lost. Automatic compaction is deliberately left alone, since Pi queues and drains steered messages itself.
58
+
59
+ - **A scoped session lookup no longer reads every session on the machine.** `pi-link <name>`, `--resolve` and `--list` fully parsed every session file in every directory before filtering by cwd, so the wait grew with total history rather than with the project: on a corpus of 591 sessions (about 863 MiB) a local lookup took roughly 3.6-3.9 seconds, of which 88-100% was spent reading sessions belonging to other directories. A scoped scan now stops at the first complete session header and abandons the file when its immutable cwd names another directory, which brings the same corpus to well under a second. Sessions in scope are still read in full, so the latest `link-name` still wins and `--list` counts are unchanged, and `--global` keeps its existing full-scan behavior. One consequence: a local miss can no longer count same-name sessions in other cwds, because their names live past the point the scan stops. Instead of `(N matches in other cwds ...)` it now advises `Use --global to search other cwds.` without claiming that anything is there.
60
+
61
+ - **A steady stream of link messages can no longer postpone its own delivery.** The inbox timer was rearmed by every arrival, so traffic whose gaps stayed under the 200-millisecond batching window kept pushing the deadline back and the batch could wait indefinitely - which is not what the window was documented to do. The window is now fixed: the first queued message sets the deadline and later arrivals do not move it, so a sustained stream is delivered window by window. Batch size and character caps, arrival order, sender labels, the overflow drain and the compaction hold are unchanged.
62
+
63
+ - **Connecting is owned by one cancellable attempt, so nothing establishes behind the user's back.** A terminal stays `disconnected` for as long as a dial or a bind takes, which made that state useless as a guard: a second `/link-connect`, a retry timer or a startup connect could open another socket and register the same terminal twice, a socket that opened after `/link-disconnect` still joined the link, a superseded socket's close could tear down the connection that replaced it, and a server that finished binding after a disconnect still became the hub. One private attempt now owns every pending transport across the client-then-hub sequence; callers arriving while it runs join it instead of starting their own; disconnect and shutdown invalidate it and physically close what it had pending; and only the established socket or server may deliver messages, accept clients or clear connection state. The opening handshake is also bounded at 5 seconds, so a listener that accepts the connection and never upgrades no longer leaves the terminal offline forever with no retry - it fails the attempt and the fallback continues. Delivery, routing, naming and the 2-5 second retry are unchanged.
64
+
65
+ - **A remote compact request can no longer land on a terminal Pi is still working.** Pi's own idle state now authorizes the request instead of pi-link's view of the agent run. `agent_end` arrives while Pi may still auto-retry, run an automatic compaction, or drain a queued continuation inside the same run, and a request accepted in that window called `ctx.compact()` concurrently with work already in progress — which aborts it and compacts the same branch twice. Requests arriving then are declined as `busy`. The narrower window before Pi announces a *local manual* compaction remains open, and remains upstream debt: Pi aborts, authorizes and prepares before it tells extensions anything, and a compaction of its own is not reentrancy-safe.
66
+
67
+ - **A terminal reports `thinking` until Pi says the run is settled, not until the visible turn ends.** It previously went `idle` at `agent_end`, advertising itself as free while an automatic retry, an automatic compaction or a queued continuation was still running, and `link_list` showed that to every peer. Idle is now published on Pi's `agent_settled`, and only when that event's context still reports the session idle — so a run another extension starts during settlement keeps reporting `thinking` instead of being overwritten. A tool call left unmatched when the turn ends still falls back to `thinking` rather than staying pinned. Message delivery is unchanged: automatic compaction stays ungated and Pi keeps owning the steering and draining of link messages.
68
+
69
+ - **Status stays truthful while several tools run at once.** Active calls are tracked per call, so `tool:<name>` names the first still-active call Pi reported, advances only when that displayed call ends, and stays a tool status until the last call ends. Previously one call finishing could drop the reported status back to `thinking` while others were still running. Two calls with the same tool name hand over without resetting the reported duration. Nothing changed on the wire or in how status is displayed.
70
+
71
+ ---
72
+
9
73
  ## 0.2.0 — 2026-07-17
10
74
 
11
75
  ### Added