pi-link 0.5.1 → 0.5.2
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 +16 -0
- package/bin/pi-link.mjs +13 -12
- package/index.ts +14 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.5.2 — 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Session-directory errors no longer look like an empty session history.** If the CLI cannot enumerate the session root for a reason other than a missing directory, it reports the error and exits with code `1`. The launcher no longer starts a new session on that false absence, `--list` no longer reports no sessions, and `--resolve` no longer reports the name as not found. A missing root still means no sessions yet; handling of individual session files and subdirectories is unchanged.
|
|
14
|
+
|
|
15
|
+
- **A terminal named `__proto__` keeps its status, cwd and context snapshots.** The name-keyed records in the initial welcome and `link_list` details now use dictionaries without a prototype, so that accepted name is copied into those records and serialized as an ordinary own property rather than lost through JavaScript's inherited setter. No names are newly reserved.
|
|
16
|
+
|
|
17
|
+
- **`pi-link --version` fails explicitly when package metadata cannot supply a version.** An unreadable or malformed `package.json`, or a missing version, now produces an error and a nonzero exit instead of printing `unknown` and exiting successfully. A healthy installation still prints only its version.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Internal cleanup reuses existing status and delivery helpers.** Status formatting uses the existing identity formatter, disconnect relies on the inbox scheduler's existing timer guard, and client status updates use the shared hub broadcast path instead of duplicating it. Those updates keep their authoritative sender name, recipients and context rules; the hub now stores them through self-delivery after sending to peers, as the shared path already does. Two unreachable CLI `break` statements after `process.exit(0)` were also removed.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
9
25
|
## 0.5.1 — 2026-09-22
|
|
10
26
|
|
|
11
27
|
### Fixed
|
package/bin/pi-link.mjs
CHANGED
|
@@ -183,14 +183,17 @@ async function loadSessionRecord(filePath, scopeCwd) {
|
|
|
183
183
|
// Returns meta + mtime + path for every readable session in `dir`, or only
|
|
184
184
|
// those from `scopeCwd` when it is given. Custom layout is flat
|
|
185
185
|
// (<dir>/*.jsonl); default layout has one subdir level per encoded cwd
|
|
186
|
-
// (<dir>/<sub>/*.jsonl).
|
|
187
|
-
//
|
|
186
|
+
// (<dir>/<sub>/*.jsonl). A missing `dir` means no sessions yet; any other
|
|
187
|
+
// failure to read it is fatal, since reporting no sessions would be a lie.
|
|
188
|
+
// Errors on individual files/dirs are silently skipped — active or
|
|
189
|
+
// partially-written sessions are tolerated.
|
|
188
190
|
async function scanSessions(dir, isCustom, scopeCwd) {
|
|
189
191
|
let entries;
|
|
190
192
|
try {
|
|
191
193
|
entries = await readdir(dir, { withFileTypes: true });
|
|
192
|
-
} catch {
|
|
193
|
-
return [];
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err.code === "ENOENT") return [];
|
|
196
|
+
fail(`cannot read session directory: ${err.message}`);
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
const tasks = [];
|
|
@@ -315,14 +318,14 @@ function printHelp() {
|
|
|
315
318
|
}
|
|
316
319
|
|
|
317
320
|
function printVersion() {
|
|
321
|
+
let pkg;
|
|
318
322
|
try {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
322
|
-
console.log(pkg.version ?? "unknown");
|
|
323
|
-
} catch {
|
|
324
|
-
console.log("unknown");
|
|
323
|
+
pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
324
|
+
} catch (err) {
|
|
325
|
+
fail(`cannot read pi-link's package.json: ${err.message}`);
|
|
325
326
|
}
|
|
327
|
+
if (!pkg.version) fail("pi-link's package.json has no version");
|
|
328
|
+
console.log(pkg.version);
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
function describeMode(mode) {
|
|
@@ -515,11 +518,9 @@ switch (state.mode) {
|
|
|
515
518
|
case "help":
|
|
516
519
|
printHelp();
|
|
517
520
|
process.exit(0);
|
|
518
|
-
break; // unreachable; present to satisfy no-fallthrough lints
|
|
519
521
|
case "version":
|
|
520
522
|
printVersion();
|
|
521
523
|
process.exit(0);
|
|
522
|
-
break; // unreachable; present to satisfy no-fallthrough lints
|
|
523
524
|
case "list":
|
|
524
525
|
await runList(state);
|
|
525
526
|
break;
|
package/index.ts
CHANGED
|
@@ -421,9 +421,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
function formatStatus(s: LinkStatus): string {
|
|
424
|
-
|
|
425
|
-
if (s.kind === "tool") return `tool:${s.toolName} (${dur})`;
|
|
426
|
-
return `${s.kind} (${dur})`;
|
|
424
|
+
return `${statusIdentity(s)} (${formatDuration(s.since)})`;
|
|
427
425
|
}
|
|
428
426
|
|
|
429
427
|
function formatTokens(n: number): string {
|
|
@@ -963,14 +961,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
963
961
|
hubClients.set(clientWs, clientName);
|
|
964
962
|
const list = terminalList();
|
|
965
963
|
|
|
966
|
-
// Confirm to the new client (with status, cwd and context snapshots)
|
|
967
|
-
|
|
964
|
+
// Confirm to the new client (with status, cwd and context snapshots).
|
|
965
|
+
// Keyed by terminal name, so no prototype: a plain {} would swallow "__proto__".
|
|
966
|
+
const statuses: Record<string, LinkStatus> = Object.create(null);
|
|
968
967
|
statuses[terminalName] = deriveStatus(); // hub's own status
|
|
969
968
|
for (const [name, status] of terminalStatuses) statuses[name] = status;
|
|
970
|
-
const cwds: Record<string, string> =
|
|
969
|
+
const cwds: Record<string, string> = Object.create(null);
|
|
971
970
|
if (currentCwd) cwds[terminalName] = currentCwd; // hub's own cwd
|
|
972
971
|
for (const [name, cwd] of terminalCwds) cwds[name] = cwd;
|
|
973
|
-
const contexts: Record<string, ContextSnapshot> =
|
|
972
|
+
const contexts: Record<string, ContextSnapshot> = Object.create(null);
|
|
974
973
|
const hubContext = captureContext();
|
|
975
974
|
if (hubContext) contexts[terminalName] = hubContext; // hub's own context
|
|
976
975
|
for (const [name, c] of terminalContexts) contexts[name] = c;
|
|
@@ -1001,21 +1000,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1001
1000
|
// Ignore messages from unregistered clients
|
|
1002
1001
|
if (!clientName) return;
|
|
1003
1002
|
|
|
1004
|
-
// Status update
|
|
1003
|
+
// Status update: rebuilt under the authoritative name, fanned out to the other
|
|
1004
|
+
// clients, and stored by the hub through its own self-delivery.
|
|
1005
1005
|
if (msg.type === "status_update") {
|
|
1006
|
-
terminalStatuses.set(clientName, msg.status);
|
|
1007
|
-
if (msg.context) terminalContexts.set(clientName, msg.context);
|
|
1008
|
-
else if (msg.context === null) terminalContexts.delete(clientName);
|
|
1009
1006
|
const normalized: StatusUpdateMsg = {
|
|
1010
1007
|
type: "status_update",
|
|
1011
1008
|
name: clientName,
|
|
1012
1009
|
status: msg.status,
|
|
1013
1010
|
context: msg.context, // undefined omitted by JSON; null forwarded to clear
|
|
1014
1011
|
};
|
|
1015
|
-
|
|
1016
|
-
for (const [otherWs, name] of hubClients) {
|
|
1017
|
-
if (name !== clientName) otherWs.send(json);
|
|
1018
|
-
}
|
|
1012
|
+
hubBroadcast(normalized, clientName);
|
|
1019
1013
|
return;
|
|
1020
1014
|
}
|
|
1021
1015
|
|
|
@@ -1340,7 +1334,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1340
1334
|
updateStatus();
|
|
1341
1335
|
|
|
1342
1336
|
// Inbox survives disconnect; flush unless a local /compact still gates it.
|
|
1343
|
-
|
|
1337
|
+
releaseInbox();
|
|
1344
1338
|
}
|
|
1345
1339
|
|
|
1346
1340
|
function cleanup() {
|
|
@@ -1765,9 +1759,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1765
1759
|
async execute() {
|
|
1766
1760
|
if (role === "disconnected") return notConnectedResult();
|
|
1767
1761
|
|
|
1768
|
-
|
|
1769
|
-
const
|
|
1770
|
-
const
|
|
1762
|
+
// Keyed by terminal name, so no prototype (see the welcome snapshots).
|
|
1763
|
+
const statuses: Record<string, string> = Object.create(null);
|
|
1764
|
+
const cwds: Record<string, string> = Object.create(null);
|
|
1765
|
+
const contexts: Record<string, ContextSnapshot> = Object.create(null);
|
|
1771
1766
|
const visible = visibleTerminals();
|
|
1772
1767
|
const list = visible
|
|
1773
1768
|
.map((name) => {
|
package/package.json
CHANGED