linkshell-cli 0.5.24 → 0.5.26

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.
@@ -36,6 +36,13 @@ export declare class AgentWorkspaceProxy {
36
36
  });
37
37
  handleEnvelope(envelope: Envelope): Promise<void>;
38
38
  stop(): void;
39
+ private fsWatchers;
40
+ private fsSyncTimer;
41
+ private fsSyncRunning;
42
+ private static readonly FS_SYNC_THROTTLE_MS;
43
+ private startSessionWatchers;
44
+ private scheduleSessionSync;
45
+ private syncAndPushSessions;
39
46
  private clientForProvider;
40
47
  private shouldRecycleProviderClient;
41
48
  private recycleProviderClient;
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, join, relative } from "node:path";
4
4
  import { createEnvelope, parseTypedPayload, } from "@linkshell/protocol";
@@ -1660,11 +1660,88 @@ export class AgentWorkspaceProxy {
1660
1660
  }
1661
1661
  }
1662
1662
  stop() {
1663
+ for (const watcher of this.fsWatchers) {
1664
+ try {
1665
+ watcher.close();
1666
+ }
1667
+ catch { /* already closed */ }
1668
+ }
1669
+ this.fsWatchers = [];
1670
+ if (this.fsSyncTimer) {
1671
+ clearTimeout(this.fsSyncTimer);
1672
+ this.fsSyncTimer = undefined;
1673
+ }
1663
1674
  for (const client of this.clients.values()) {
1664
1675
  client.stop();
1665
1676
  }
1666
1677
  this.clients.clear();
1667
1678
  }
1679
+ // ── External-session live refresh ────────────────────────────────────
1680
+ // The console requests the session list only once, at connect. Without this,
1681
+ // a session another `claude`/`codex` process starts (or whose status changes)
1682
+ // after connect never shows up — the tree is frozen at connect time. Watch the
1683
+ // on-disk session stores and re-sync + push when they change, so externally-
1684
+ // driven sessions surface live (this is what CodeIsland does). Event-driven:
1685
+ // zero cost while idle, throttled to at most one re-scan per window during a
1686
+ // burst of writes (an active turn appends constantly).
1687
+ fsWatchers = [];
1688
+ fsSyncTimer;
1689
+ fsSyncRunning = false;
1690
+ static FS_SYNC_THROTTLE_MS = 1500;
1691
+ startSessionWatchers() {
1692
+ if (this.fsWatchers.length > 0)
1693
+ return; // already watching
1694
+ const roots = [
1695
+ join(homedir(), ".claude", "projects"),
1696
+ join(homedir(), ".codex", "sessions"),
1697
+ ];
1698
+ for (const root of roots) {
1699
+ if (!existsSync(root))
1700
+ continue;
1701
+ try {
1702
+ // recursive is supported on macOS/Windows; on Linux this throws, and we
1703
+ // simply don't get live updates (the connect-time scan still works).
1704
+ const watcher = watch(root, { recursive: true, persistent: false }, () => this.scheduleSessionSync());
1705
+ watcher.on("error", () => { });
1706
+ this.fsWatchers.push(watcher);
1707
+ }
1708
+ catch {
1709
+ // recursive watch unavailable on this platform — skip silently.
1710
+ }
1711
+ }
1712
+ }
1713
+ scheduleSessionSync() {
1714
+ if (this.fsSyncTimer)
1715
+ return; // a sync is already scheduled in this window
1716
+ this.fsSyncTimer = setTimeout(() => {
1717
+ this.fsSyncTimer = undefined;
1718
+ void this.syncAndPushSessions();
1719
+ }, AgentWorkspaceProxy.FS_SYNC_THROTTLE_MS);
1720
+ }
1721
+ async syncAndPushSessions() {
1722
+ if (this.fsSyncRunning)
1723
+ return; // don't overlap with an in-flight sync
1724
+ this.fsSyncRunning = true;
1725
+ try {
1726
+ await this.syncProviderSessions();
1727
+ // Push the conversation list (statuses) — lighter than a full snapshot and
1728
+ // exactly what refreshes the tree. The web handles an unsolicited
1729
+ // conversation.list.result the same as a requested one.
1730
+ this.input.send(createEnvelope({
1731
+ type: "agent.v2.conversation.list.result",
1732
+ sessionId: this.input.sessionId,
1733
+ payload: { conversations: [...this.conversations.values()].filter((c) => !c.archived) },
1734
+ }));
1735
+ }
1736
+ catch (error) {
1737
+ if (this.input.verbose) {
1738
+ process.stderr.write(`[agent:v2] fs-watch sync failed: ${error instanceof Error ? error.message : String(error)}\n`);
1739
+ }
1740
+ }
1741
+ finally {
1742
+ this.fsSyncRunning = false;
1743
+ }
1744
+ }
1668
1745
  clientForProvider(provider) {
1669
1746
  return this.clients.get(provider);
1670
1747
  }
@@ -1710,6 +1787,9 @@ export class AgentWorkspaceProxy {
1710
1787
  this.status = "idle";
1711
1788
  this.error = undefined;
1712
1789
  this.sendCapabilities();
1790
+ // Start watching the on-disk session stores so externally-started sessions
1791
+ // and status changes surface live, not just at connect time.
1792
+ this.startSessionWatchers();
1713
1793
  }
1714
1794
  async ensureProviderClient(provider) {
1715
1795
  const existing = this.clients.get(provider);