pi-link 0.1.4 → 0.1.5
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 +11 -1
- package/README.md +42 -27
- package/index.ts +134 -25
- package/package.json +1 -1
- package/sync.ffs_db +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-link are documented here.
|
|
4
4
|
|
|
5
|
-
This changelog is based on the git history from `2026-03-21`
|
|
5
|
+
This changelog is based on the git history from `2026-03-21` through `2026-04-02` (current). Versions correspond to npm publishes.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0.1.5 — 2026-04-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Working directory sharing.** Each terminal reports its `cwd` on connect and on session switch. New `cwd_update` protocol message (10th message type) broadcasts mid-session directory changes. `link_list` and `/link` now show per-terminal working directories — full absolute paths in tool output, `~/…` shortened in the TUI. Agents can use this to choose the right target, use explicit paths when terminals differ, and catch wrong-project mistakes early.
|
|
14
|
+
|
|
15
|
+
- **Header comment cleanup.** Simplified the top-of-file doc comment — removed feature bullet list and install instructions in favor of a concise summary.
|
|
6
16
|
|
|
7
17
|
---
|
|
8
18
|
|
package/README.md
CHANGED
|
@@ -85,7 +85,9 @@ Here's a concrete example of two terminals collaborating. Open two separate `pi
|
|
|
85
85
|
> /link
|
|
86
86
|
⚡ Link: builder (hub) · 2 online
|
|
87
87
|
builder: idle (5s)
|
|
88
|
+
cwd: ~/my-project
|
|
88
89
|
researcher: idle (12s)
|
|
90
|
+
cwd: ~/my-project
|
|
89
91
|
```
|
|
90
92
|
|
|
91
93
|
**Terminal 2** — rename it too:
|
|
@@ -142,11 +144,11 @@ The extension registers three tools that the LLM can invoke during agent runs.
|
|
|
142
144
|
|
|
143
145
|
### Which tool should I use?
|
|
144
146
|
|
|
145
|
-
| Tool | Behavior | Returns
|
|
146
|
-
| ------------- | ---------------------------------------------------- |
|
|
147
|
-
| `link_send` | Send a message; optionally trigger the remote LLM | Send/delivery status only
|
|
148
|
-
| `link_prompt` | Run a prompt on a remote terminal and wait for reply | The remote terminal's assistant response
|
|
149
|
-
| `link_list` | List currently connected terminals | Terminal
|
|
147
|
+
| Tool | Behavior | Returns |
|
|
148
|
+
| ------------- | ---------------------------------------------------- | ----------------------------------------- |
|
|
149
|
+
| `link_send` | Send a message; optionally trigger the remote LLM | Send/delivery status only |
|
|
150
|
+
| `link_prompt` | Run a prompt on a remote terminal and wait for reply | The remote terminal's assistant response |
|
|
151
|
+
| `link_list` | List currently connected terminals | Terminal list with roles, status, and cwd |
|
|
150
152
|
|
|
151
153
|
**If you need the other terminal's answer back, use `link_prompt`.** Use `link_send` to notify or steer without waiting.
|
|
152
154
|
|
|
@@ -187,7 +189,9 @@ Send a prompt to a remote terminal and **wait** for the LLM's response (synchron
|
|
|
187
189
|
|
|
188
190
|
### `link_list`
|
|
189
191
|
|
|
190
|
-
Lists all connected terminals with role info, live agent status, and self-identification. Takes no parameters.
|
|
192
|
+
Lists all connected terminals with role info, live agent status, working directory, and self-identification. Takes no parameters.
|
|
193
|
+
|
|
194
|
+
Each terminal reports its current working directory on connect and on session switch. `link_list` shows the full absolute path so agents can choose the right target, use explicit paths when terminals differ, and catch wrong-project mistakes early.
|
|
191
195
|
|
|
192
196
|
Each terminal's status is derived automatically from Pi lifecycle events — agents can't set it manually. Three states:
|
|
193
197
|
|
|
@@ -199,13 +203,18 @@ Each terminal's status is derived automatically from Pi lifecycle events — age
|
|
|
199
203
|
|
|
200
204
|
Durations are computed at render time from a `since` timestamp — no timer traffic over the wire. Terminals that just joined with no status data yet render as blank, not fake idle.
|
|
201
205
|
|
|
206
|
+
Working directories use full absolute paths in tool output. In the TUI (`/link`), paths are shortened to `~/...` when possible to keep the display compact.
|
|
207
|
+
|
|
202
208
|
**Example output:**
|
|
203
209
|
|
|
204
210
|
```
|
|
205
|
-
|
|
206
|
-
•
|
|
207
|
-
|
|
208
|
-
•
|
|
211
|
+
Connected terminals:
|
|
212
|
+
• opus@pi-link (you) idle (12s)
|
|
213
|
+
cwd: C:\Users\andre\.pi
|
|
214
|
+
• gpt@pi-link thinking (3s)
|
|
215
|
+
cwd: C:\Users\andre\.pi
|
|
216
|
+
• docs@pi-link idle (1m)
|
|
217
|
+
cwd: C:\Users\andre\.pi
|
|
209
218
|
```
|
|
210
219
|
|
|
211
220
|
---
|
|
@@ -214,7 +223,7 @@ link (hub) 3 terminal(s)
|
|
|
214
223
|
|
|
215
224
|
| Command | Purpose |
|
|
216
225
|
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
217
|
-
| `/link` | Show link status (name, role, online count, agent status per terminal)
|
|
226
|
+
| `/link` | Show link status (name, role, online count, agent status, and cwd per terminal) |
|
|
218
227
|
| `/link-name [name]` | Rename and save as this session's preferred link name. With no argument, adopts the Pi session name. Restored on resume. |
|
|
219
228
|
| `/link-broadcast <msg>` | Broadcast a chat message to all other terminals |
|
|
220
229
|
| `/link-connect` | Connect to Pi Link (works anytime, with or without `--link`) |
|
|
@@ -226,8 +235,11 @@ link (hub) 3 terminal(s)
|
|
|
226
235
|
> /link
|
|
227
236
|
⚡ Link: builder (hub) · 3 online
|
|
228
237
|
builder: idle (12s)
|
|
238
|
+
cwd: ~/my-project
|
|
229
239
|
worker-1: thinking (3s)
|
|
240
|
+
cwd: ~/my-project
|
|
230
241
|
worker-2: tool:bash (5s)
|
|
242
|
+
cwd: ~/other-project
|
|
231
243
|
|
|
232
244
|
> /link-name orchestrator
|
|
233
245
|
✓ Renamed to "orchestrator"
|
|
@@ -385,19 +397,20 @@ The `pi.extensions` field tells Pi which files to load as extensions. Here it po
|
|
|
385
397
|
|
|
386
398
|
### Protocol
|
|
387
399
|
|
|
388
|
-
The wire protocol consists of **
|
|
389
|
-
|
|
390
|
-
| Type | Direction | Purpose
|
|
391
|
-
| ----------------- | --------------- |
|
|
392
|
-
| `register` | Client → Hub | First message after connecting; requests a name
|
|
393
|
-
| `welcome` | Hub → Client | Confirms assigned name, terminal list + status
|
|
394
|
-
| `terminal_joined` | Hub → All | Broadcast when a terminal joins
|
|
395
|
-
| `terminal_left` | Hub → All | Broadcast when a terminal disconnects
|
|
396
|
-
| `chat` | Any → Any/All | Fire-and-forget message; optionally triggers LLM turn
|
|
397
|
-
| `prompt_request` | Any → Any | Request a remote terminal to execute a prompt
|
|
398
|
-
| `prompt_response` | Any → Any | Response carrying the remote prompt result
|
|
399
|
-
| `status_update` | Any → Hub → All | Terminal broadcasts its agent status change
|
|
400
|
-
| `
|
|
400
|
+
The wire protocol consists of **10 message types**, all serialized as JSON over WebSocket frames. New cwd-related fields are optional for backward compatibility.
|
|
401
|
+
|
|
402
|
+
| Type | Direction | Purpose |
|
|
403
|
+
| ----------------- | --------------- | ----------------------------------------------------------------------- |
|
|
404
|
+
| `register` | Client → Hub | First message after connecting; requests a name, optionally reports cwd |
|
|
405
|
+
| `welcome` | Hub → Client | Confirms assigned name, terminal list + status/cwd snapshots |
|
|
406
|
+
| `terminal_joined` | Hub → All | Broadcast when a terminal joins; may include cwd |
|
|
407
|
+
| `terminal_left` | Hub → All | Broadcast when a terminal disconnects |
|
|
408
|
+
| `chat` | Any → Any/All | Fire-and-forget message; optionally triggers LLM turn |
|
|
409
|
+
| `prompt_request` | Any → Any | Request a remote terminal to execute a prompt |
|
|
410
|
+
| `prompt_response` | Any → Any | Response carrying the remote prompt result |
|
|
411
|
+
| `status_update` | Any → Hub → All | Terminal broadcasts its agent status change |
|
|
412
|
+
| `cwd_update` | Any → Hub → All | Terminal broadcasts a cwd change |
|
|
413
|
+
| `error` | Hub → Client | Error notification |
|
|
401
414
|
|
|
402
415
|
### Message Flow Examples
|
|
403
416
|
|
|
@@ -406,16 +419,17 @@ The wire protocol consists of **9 message types**, all serialized as JSON over W
|
|
|
406
419
|
```
|
|
407
420
|
Client Hub
|
|
408
421
|
| |
|
|
409
|
-
| register {name:"builder"
|
|
422
|
+
| register {name:"builder", |
|
|
423
|
+
| cwd:"C:\\Users\\..."} |
|
|
410
424
|
|---------------------------->|
|
|
411
425
|
| |
|
|
412
426
|
| welcome {name, terminals, |
|
|
413
|
-
| statuses}
|
|
427
|
+
| statuses, cwds} |
|
|
414
428
|
|<----------------------------|
|
|
415
429
|
| |
|
|
416
430
|
```
|
|
417
431
|
|
|
418
|
-
Hub then broadcasts `terminal_joined` to the other connected terminals. The `welcome` message includes
|
|
432
|
+
Hub then broadcasts `terminal_joined` to the other connected terminals. The `welcome` message includes status and cwd snapshots for all connected terminals (fields omitted above for brevity). `terminal_joined` also includes the new terminal's optional cwd, and mid-session cwd changes are distributed via `cwd_update`.
|
|
419
433
|
|
|
420
434
|
**Sending a chat message:**
|
|
421
435
|
|
|
@@ -468,6 +482,7 @@ Default names are random 4-character hex IDs: `t-a1b2`, `t-c3d4`, etc.
|
|
|
468
482
|
| `agentRunning` | `boolean` | Whether an agent run is active; blocks incoming remote prompts |
|
|
469
483
|
| `activeToolName` | `string \| null` | Name of the currently executing tool (drives `tool:<name>` status) |
|
|
470
484
|
| `stateSince` | `number` | Timestamp of last status change (used for duration display) |
|
|
485
|
+
| `currentCwd` | `string` | Current working directory reported to peers on connect and session switch |
|
|
471
486
|
| `manuallyDisconnected` | `boolean` | Set by `/link-disconnect`; suppresses auto-reconnect |
|
|
472
487
|
| `pendingRemotePrompt` | `object \| null` | Tracks the single in-flight remote prompt execution |
|
|
473
488
|
| `pendingPromptResponses` | `Map` | Outstanding prompt RPCs awaiting responses (includes inactivity + ceiling timers per entry) |
|
package/index.ts
CHANGED
|
@@ -2,21 +2,12 @@
|
|
|
2
2
|
* Pi Link — WebSocket-based inter-terminal communication
|
|
3
3
|
*
|
|
4
4
|
* Connects multiple Pi terminals over a local WebSocket link.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Opt-in via --link flag or /link-connect command.
|
|
6
|
+
* First terminal to connect becomes the hub; others join as clients.
|
|
7
|
+
* Hub loss triggers automatic promotion of a surviving client.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - Named terminals with uniqueness enforcement
|
|
11
|
-
* - LLM tools: link_send (chat), link_prompt (remote prompt + response), link_list
|
|
12
|
-
* - Commands: /link, /link-name, /link-broadcast, /link-connect, /link-disconnect
|
|
13
|
-
* - Custom message renderer for incoming link messages
|
|
14
|
-
* - Auto-reconnect with hub promotion on disconnect
|
|
15
|
-
*
|
|
16
|
-
* Install:
|
|
17
|
-
* cd ~/.pi/agent/extensions/pi-link && npm install
|
|
18
|
-
*
|
|
19
|
-
* Then just start two or more `pi` terminals — they discover each other.
|
|
9
|
+
* Tools: link_send, link_prompt, link_list
|
|
10
|
+
* Commands: /link, /link-name, /link-broadcast, /link-connect, /link-disconnect
|
|
20
11
|
*/
|
|
21
12
|
|
|
22
13
|
import type {
|
|
@@ -26,6 +17,7 @@ import type {
|
|
|
26
17
|
import { Text } from "@mariozechner/pi-tui";
|
|
27
18
|
import { Type } from "@sinclair/typebox";
|
|
28
19
|
import * as crypto from "node:crypto";
|
|
20
|
+
import * as os from "node:os";
|
|
29
21
|
|
|
30
22
|
import { WebSocket, WebSocketServer } from "ws";
|
|
31
23
|
|
|
@@ -42,17 +34,25 @@ const KEEPALIVE_INTERVAL_MS = 30_000;
|
|
|
42
34
|
interface RegisterMsg {
|
|
43
35
|
type: "register";
|
|
44
36
|
name: string;
|
|
37
|
+
cwd?: string;
|
|
45
38
|
}
|
|
46
39
|
interface WelcomeMsg {
|
|
47
40
|
type: "welcome";
|
|
48
41
|
name: string;
|
|
49
42
|
terminals: string[];
|
|
50
43
|
statuses?: Record<string, LinkStatus>;
|
|
44
|
+
cwds?: Record<string, string>;
|
|
51
45
|
}
|
|
52
46
|
interface TerminalJoinedMsg {
|
|
53
47
|
type: "terminal_joined";
|
|
54
48
|
name: string;
|
|
55
49
|
terminals: string[];
|
|
50
|
+
cwd?: string;
|
|
51
|
+
}
|
|
52
|
+
interface CwdUpdateMsg {
|
|
53
|
+
type: "cwd_update";
|
|
54
|
+
name: string;
|
|
55
|
+
cwd: string;
|
|
56
56
|
}
|
|
57
57
|
interface TerminalLeftMsg {
|
|
58
58
|
type: "terminal_left";
|
|
@@ -105,6 +105,7 @@ type LinkMessage =
|
|
|
105
105
|
| PromptRequestMsg
|
|
106
106
|
| PromptResponseMsg
|
|
107
107
|
| StatusUpdateMsg
|
|
108
|
+
| CwdUpdateMsg
|
|
108
109
|
| ErrorMsg;
|
|
109
110
|
|
|
110
111
|
// ─── Extension ───────────────────────────────────────────────────────────────
|
|
@@ -134,11 +135,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
134
135
|
let lastPushedKind: string | null = null;
|
|
135
136
|
let lastPushedTool: string | null = null;
|
|
136
137
|
const terminalStatuses = new Map<string, LinkStatus>(); // other terminals
|
|
138
|
+
let currentCwd = "";
|
|
139
|
+
const terminalCwds = new Map<string, string>(); // other terminals' cwds
|
|
137
140
|
|
|
138
141
|
// Hub state
|
|
139
142
|
let wss: WebSocketServer | null = null;
|
|
140
143
|
const hubClients = new Map<WebSocket, string>(); // ws → terminal name
|
|
141
144
|
const hubTerminalStatuses = new Map<string, LinkStatus>(); // hub-authoritative
|
|
145
|
+
const hubTerminalCwds = new Map<string, string>(); // hub-authoritative (excludes self)
|
|
142
146
|
|
|
143
147
|
// Client state
|
|
144
148
|
let ws: WebSocket | null = null;
|
|
@@ -221,6 +225,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
221
225
|
return map.get(name) ?? null;
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
function getCwdFor(name: string): string | null {
|
|
229
|
+
if (name === terminalName) return currentCwd || null;
|
|
230
|
+
if (role === "hub") return hubTerminalCwds.get(name) ?? null;
|
|
231
|
+
return terminalCwds.get(name) ?? null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function shortenPath(cwd: string): string {
|
|
235
|
+
const home = os.homedir().replace(/\\/g, "/");
|
|
236
|
+
const normalized = cwd.replace(/\\/g, "/");
|
|
237
|
+
if (normalized === home) return "~";
|
|
238
|
+
if (normalized.startsWith(home + "/"))
|
|
239
|
+
return "~" + normalized.slice(home.length);
|
|
240
|
+
return normalized;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function pushCwdUpdate() {
|
|
244
|
+
const msg: CwdUpdateMsg = {
|
|
245
|
+
type: "cwd_update",
|
|
246
|
+
name: terminalName,
|
|
247
|
+
cwd: currentCwd,
|
|
248
|
+
};
|
|
249
|
+
if (role === "hub") {
|
|
250
|
+
hubBroadcast(msg, terminalName);
|
|
251
|
+
} else if (role === "client" && ws?.readyState === WebSocket.OPEN) {
|
|
252
|
+
ws.send(JSON.stringify(msg));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
224
256
|
// ── Pending prompt helpers ───────────────────────────────────────────────
|
|
225
257
|
|
|
226
258
|
function cleanupPending(requestId: string) {
|
|
@@ -365,11 +397,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
365
397
|
terminalName = msg.name;
|
|
366
398
|
connectedTerminals = msg.terminals;
|
|
367
399
|
terminalStatuses.clear();
|
|
400
|
+
terminalCwds.clear();
|
|
368
401
|
if (msg.statuses) {
|
|
369
402
|
for (const [name, status] of Object.entries(msg.statuses)) {
|
|
370
403
|
terminalStatuses.set(name, status);
|
|
371
404
|
}
|
|
372
405
|
}
|
|
406
|
+
if (msg.cwds) {
|
|
407
|
+
for (const [name, cwd] of Object.entries(msg.cwds)) {
|
|
408
|
+
terminalCwds.set(name, cwd);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
373
411
|
updateStatus();
|
|
374
412
|
ctx?.ui.notify(
|
|
375
413
|
`Joined link as "${terminalName}" (${connectedTerminals.length} online)`,
|
|
@@ -378,9 +416,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
416
|
pushStatus(true);
|
|
379
417
|
break;
|
|
380
418
|
|
|
381
|
-
// ──
|
|
419
|
+
// ── Membership updates ──
|
|
382
420
|
case "terminal_joined":
|
|
383
421
|
connectedTerminals = msg.terminals;
|
|
422
|
+
if (role !== "hub" && msg.cwd) terminalCwds.set(msg.name, msg.cwd);
|
|
384
423
|
updateStatus();
|
|
385
424
|
ctx?.ui.notify(`"${msg.name}" joined the link`, "info");
|
|
386
425
|
break;
|
|
@@ -388,6 +427,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
388
427
|
case "terminal_left":
|
|
389
428
|
connectedTerminals = msg.terminals;
|
|
390
429
|
terminalStatuses.delete(msg.name);
|
|
430
|
+
if (role !== "hub") terminalCwds.delete(msg.name);
|
|
391
431
|
// Fail any pending prompts to the departed terminal immediately
|
|
392
432
|
for (const [id, pending] of pendingPromptResponses) {
|
|
393
433
|
if (pending.targetName === msg.name) {
|
|
@@ -412,6 +452,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
412
452
|
resetInactivityFor(msg.name);
|
|
413
453
|
break;
|
|
414
454
|
|
|
455
|
+
case "cwd_update":
|
|
456
|
+
terminalCwds.set(msg.name, msg.cwd);
|
|
457
|
+
break;
|
|
458
|
+
|
|
415
459
|
// ── Chat message ──
|
|
416
460
|
case "chat":
|
|
417
461
|
pi.sendMessage(
|
|
@@ -488,30 +532,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
488
532
|
if (msg.type === "register") {
|
|
489
533
|
clientName = uniqueName(msg.name);
|
|
490
534
|
hubClients.set(clientWs, clientName);
|
|
535
|
+
if (msg.cwd) hubTerminalCwds.set(clientName, msg.cwd);
|
|
491
536
|
const list = terminalList();
|
|
492
537
|
connectedTerminals = list;
|
|
493
538
|
updateStatus();
|
|
494
539
|
|
|
495
|
-
// Confirm to the new client (include status
|
|
540
|
+
// Confirm to the new client (include status + cwd snapshots)
|
|
496
541
|
const statuses: Record<string, LinkStatus> = {};
|
|
497
542
|
statuses[terminalName] = deriveStatus(); // hub's own status
|
|
498
543
|
for (const [name, status] of hubTerminalStatuses) {
|
|
499
544
|
if (name !== clientName) statuses[name] = status;
|
|
500
545
|
}
|
|
546
|
+
const cwds: Record<string, string> = {};
|
|
547
|
+
if (currentCwd) cwds[terminalName] = currentCwd; // hub's own cwd
|
|
548
|
+
for (const [name, cwd] of hubTerminalCwds) {
|
|
549
|
+
if (name !== clientName) cwds[name] = cwd;
|
|
550
|
+
}
|
|
501
551
|
clientWs.send(
|
|
502
552
|
JSON.stringify({
|
|
503
553
|
type: "welcome",
|
|
504
554
|
name: clientName,
|
|
505
555
|
terminals: list,
|
|
506
556
|
statuses,
|
|
557
|
+
cwds,
|
|
507
558
|
} satisfies WelcomeMsg),
|
|
508
559
|
);
|
|
509
560
|
|
|
510
|
-
// Notify everyone else
|
|
561
|
+
// Notify everyone else (include joiner's cwd)
|
|
511
562
|
const joined: TerminalJoinedMsg = {
|
|
512
563
|
type: "terminal_joined",
|
|
513
564
|
name: clientName,
|
|
514
565
|
terminals: list,
|
|
566
|
+
cwd: msg.cwd,
|
|
515
567
|
};
|
|
516
568
|
hubBroadcast(joined, clientName);
|
|
517
569
|
return;
|
|
@@ -536,6 +588,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
536
588
|
return;
|
|
537
589
|
}
|
|
538
590
|
|
|
591
|
+
// Cwd update — store and relay to other clients only
|
|
592
|
+
if (msg.type === "cwd_update") {
|
|
593
|
+
hubTerminalCwds.set(clientName, msg.cwd);
|
|
594
|
+
const normalized: CwdUpdateMsg = {
|
|
595
|
+
type: "cwd_update",
|
|
596
|
+
name: clientName,
|
|
597
|
+
cwd: msg.cwd,
|
|
598
|
+
};
|
|
599
|
+
const json = JSON.stringify(normalized);
|
|
600
|
+
for (const [otherWs, name] of hubClients) {
|
|
601
|
+
if (name !== clientName) otherWs.send(json);
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
539
606
|
// Route chat / prompt messages
|
|
540
607
|
if (
|
|
541
608
|
msg.type === "chat" ||
|
|
@@ -550,6 +617,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
550
617
|
if (clientName) {
|
|
551
618
|
hubClients.delete(clientWs);
|
|
552
619
|
hubTerminalStatuses.delete(clientName);
|
|
620
|
+
hubTerminalCwds.delete(clientName);
|
|
553
621
|
const list = terminalList();
|
|
554
622
|
connectedTerminals = list;
|
|
555
623
|
updateStatus();
|
|
@@ -614,6 +682,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
614
682
|
JSON.stringify({
|
|
615
683
|
type: "register",
|
|
616
684
|
name: preferredName ?? terminalName,
|
|
685
|
+
cwd: currentCwd || undefined,
|
|
617
686
|
} satisfies RegisterMsg),
|
|
618
687
|
);
|
|
619
688
|
resolve(true);
|
|
@@ -717,6 +786,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
717
786
|
connectedTerminals = [];
|
|
718
787
|
terminalStatuses.clear();
|
|
719
788
|
hubTerminalStatuses.clear();
|
|
789
|
+
terminalCwds.clear();
|
|
790
|
+
hubTerminalCwds.clear();
|
|
720
791
|
lastPushedKind = null;
|
|
721
792
|
lastPushedTool = null;
|
|
722
793
|
updateStatus();
|
|
@@ -731,6 +802,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
731
802
|
|
|
732
803
|
pi.on("session_start", async (_event, _ctx) => {
|
|
733
804
|
ctx = _ctx;
|
|
805
|
+
currentCwd = _ctx.cwd;
|
|
734
806
|
|
|
735
807
|
// Restore preferred link name from session
|
|
736
808
|
const saved = _ctx.sessionManager
|
|
@@ -755,7 +827,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
755
827
|
pi.on("session_switch", async (_event, _ctx) => {
|
|
756
828
|
ctx = _ctx;
|
|
757
829
|
|
|
758
|
-
//
|
|
830
|
+
// 1. Cwd change detection (always, before any name logic)
|
|
831
|
+
const newCwd = _ctx.cwd;
|
|
832
|
+
const cwdChanged = newCwd !== currentCwd;
|
|
833
|
+
if (cwdChanged) currentCwd = newCwd;
|
|
834
|
+
|
|
835
|
+
// 2. Restore preferred name from the new session
|
|
759
836
|
const saved = _ctx.sessionManager
|
|
760
837
|
.getEntries()
|
|
761
838
|
.filter(
|
|
@@ -766,9 +843,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
766
843
|
|
|
767
844
|
preferredName = saved?.data?.name ?? null;
|
|
768
845
|
const desiredName = preferredName ?? `t-${crypto.randomUUID().slice(0, 4)}`;
|
|
846
|
+
const nameChanged = desiredName !== terminalName;
|
|
769
847
|
|
|
770
|
-
if (
|
|
848
|
+
if (!nameChanged && !cwdChanged) return; // nothing to do
|
|
771
849
|
|
|
850
|
+
if (!nameChanged) {
|
|
851
|
+
// Name stayed the same, but cwd changed — push cwd update
|
|
852
|
+
pushCwdUpdate();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Name changed (cwd may or may not have changed too)
|
|
772
857
|
if (role === "hub") {
|
|
773
858
|
// Hub rename in-place — avoid tearing down the server
|
|
774
859
|
const takenByOther = Array.from(hubClients.values()).includes(
|
|
@@ -780,6 +865,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
780
865
|
`Session preferred name "${desiredName}" is taken, keeping "${terminalName}"`,
|
|
781
866
|
"warning",
|
|
782
867
|
);
|
|
868
|
+
// Still push cwd update under current name if cwd changed
|
|
869
|
+
if (cwdChanged) pushCwdUpdate();
|
|
783
870
|
return;
|
|
784
871
|
}
|
|
785
872
|
const old = terminalName;
|
|
@@ -793,12 +880,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
793
880
|
terminalName,
|
|
794
881
|
);
|
|
795
882
|
hubBroadcast(
|
|
796
|
-
{
|
|
883
|
+
{
|
|
884
|
+
type: "terminal_joined",
|
|
885
|
+
name: desiredName,
|
|
886
|
+
terminals: list,
|
|
887
|
+
cwd: currentCwd,
|
|
888
|
+
},
|
|
797
889
|
terminalName,
|
|
798
890
|
);
|
|
799
891
|
pushStatus(true);
|
|
800
892
|
} else if (role === "client") {
|
|
801
|
-
// Client — disconnect and reconnect with new name
|
|
893
|
+
// Client — disconnect and reconnect with new name (register includes cwd)
|
|
802
894
|
terminalName = desiredName;
|
|
803
895
|
disconnect();
|
|
804
896
|
manuallyDisconnected = false;
|
|
@@ -1099,19 +1191,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
1099
1191
|
if (role === "disconnected") return notConnectedResult();
|
|
1100
1192
|
|
|
1101
1193
|
const statuses: Record<string, string> = {};
|
|
1194
|
+
const cwds: Record<string, string> = {};
|
|
1102
1195
|
const list = connectedTerminals
|
|
1103
1196
|
.map((name) => {
|
|
1104
1197
|
const status = getStatusFor(name);
|
|
1105
1198
|
const statusStr = status ? formatStatus(status) : "";
|
|
1106
1199
|
if (statusStr) statuses[name] = statusStr;
|
|
1200
|
+
const cwd = getCwdFor(name);
|
|
1201
|
+
if (cwd) cwds[name] = cwd;
|
|
1107
1202
|
const marker = name === terminalName ? " (you)" : "";
|
|
1108
|
-
|
|
1203
|
+
let line = ` \u2022 ${name}${marker}${statusStr ? " " + statusStr : ""}`;
|
|
1204
|
+
if (cwd) line += `\n cwd: ${cwd}`;
|
|
1205
|
+
return line;
|
|
1109
1206
|
})
|
|
1110
1207
|
.join("\n");
|
|
1111
1208
|
|
|
1112
1209
|
return textResult(`Connected terminals:\n${list}`, {
|
|
1113
1210
|
terminals: connectedTerminals,
|
|
1114
1211
|
statuses,
|
|
1212
|
+
cwds,
|
|
1115
1213
|
self: terminalName,
|
|
1116
1214
|
role,
|
|
1117
1215
|
});
|
|
@@ -1122,6 +1220,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1122
1220
|
| {
|
|
1123
1221
|
terminals?: string[];
|
|
1124
1222
|
statuses?: Record<string, string>;
|
|
1223
|
+
cwds?: Record<string, string>;
|
|
1125
1224
|
self?: string;
|
|
1126
1225
|
role?: string;
|
|
1127
1226
|
}
|
|
@@ -1137,11 +1236,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1137
1236
|
for (const name of details.terminals) {
|
|
1138
1237
|
const isSelf = name === details.self;
|
|
1139
1238
|
const status = details.statuses?.[name] ?? "";
|
|
1140
|
-
const
|
|
1239
|
+
const cwd = details.cwds?.[name];
|
|
1240
|
+
const nameStr = isSelf ? `\u2022 ${name} (you)` : `\u2022 ${name}`;
|
|
1141
1241
|
text +=
|
|
1142
1242
|
"\n " +
|
|
1143
1243
|
(isSelf ? theme.fg("accent", nameStr) : theme.fg("text", nameStr)) +
|
|
1144
1244
|
(status ? " " + theme.fg("dim", status) : "");
|
|
1245
|
+
if (cwd) text += "\n " + theme.fg("dim", `cwd: ${shortenPath(cwd)}`);
|
|
1145
1246
|
}
|
|
1146
1247
|
return new Text(text, 0, 0);
|
|
1147
1248
|
},
|
|
@@ -1159,8 +1260,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1159
1260
|
const lines = connectedTerminals.map((name) => {
|
|
1160
1261
|
const status = getStatusFor(name);
|
|
1161
1262
|
const statusStr = status ? formatStatus(status) : "";
|
|
1263
|
+
const cwd = getCwdFor(name);
|
|
1162
1264
|
const marker = name === terminalName ? " (you)" : "";
|
|
1163
|
-
|
|
1265
|
+
let line = `${name}${marker}${statusStr ? ": " + statusStr : ""}`;
|
|
1266
|
+
if (cwd) line += `\n cwd: ${shortenPath(cwd)}`;
|
|
1267
|
+
return line;
|
|
1164
1268
|
});
|
|
1165
1269
|
_ctx.ui.notify(
|
|
1166
1270
|
`Link: ${terminalName} (${role}) · ${connectedTerminals.length} online\n${lines.join("\n")}`,
|
|
@@ -1225,7 +1329,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1225
1329
|
terminalName,
|
|
1226
1330
|
);
|
|
1227
1331
|
hubBroadcast(
|
|
1228
|
-
{
|
|
1332
|
+
{
|
|
1333
|
+
type: "terminal_joined",
|
|
1334
|
+
name: newName,
|
|
1335
|
+
terminals: list,
|
|
1336
|
+
cwd: currentCwd,
|
|
1337
|
+
},
|
|
1229
1338
|
terminalName,
|
|
1230
1339
|
);
|
|
1231
1340
|
pushStatus(true);
|
package/package.json
CHANGED
package/sync.ffs_db
ADDED
|
Binary file
|