opencode-zellij 0.0.8 → 0.0.10

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maou-shonen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -69,6 +69,24 @@ Close the dev server pane.
69
69
 
70
70
  `zellij_pty_request_sudo` opens a review pane, shows what will run, and waits for the user to type `YES`. The agent cannot type into that pane, so passwords and credentials stay in Zellij instead of entering prompts or tool arguments.
71
71
 
72
+ ## Configuration
73
+
74
+ Optional sidecar config files are loaded from `~/.config/opencode/opencode-zellij.config.jsonc` and from `.opencode/opencode-zellij.config.jsonc` in the current project. Project config overrides user config.
75
+
76
+ ```jsonc
77
+ {
78
+ "$schema": "https://raw.githubusercontent.com/maou-shonen/opencode-zellij/main/opencode-zellij.schema.json",
79
+ "autoUpdate": true,
80
+ "pty": {
81
+ "enabled": true,
82
+ "sudoPane": "allow" // allow, deny, or hide
83
+ },
84
+ "tabTitle": { "enabled": true }
85
+ }
86
+ ```
87
+
88
+ Set `autoUpdate`, `pty.enabled`, or `tabTitle.enabled` to `false` to turn those features off.
89
+
72
90
  ## Dynamic tab title
73
91
 
74
92
  When OpenCode runs inside Zellij, the plugin updates the current tab title to show the project, branch, and current OpenCode state:
package/README.zh.md CHANGED
@@ -69,6 +69,24 @@ OpenCode 會在啟動時自動安裝 npm plugins。Zellij 也必須已安裝,
69
69
 
70
70
  `zellij_pty_request_sudo` 會開啟 review pane、顯示即將執行的內容,並等待使用者輸入 `YES`。Agent 不能在該 pane 中輸入,因此 passwords 與 credentials 會留在 Zellij,而不是進入 prompts 或 tool arguments。
71
71
 
72
+ ## 設定
73
+
74
+ 可選的 sidecar config 會從 `~/.config/opencode/opencode-zellij.config.jsonc` 以及目前 project 的 `.opencode/opencode-zellij.config.jsonc` 載入。Project config 會覆蓋 user config。
75
+
76
+ ```jsonc
77
+ {
78
+ "$schema": "https://raw.githubusercontent.com/maou-shonen/opencode-zellij/main/opencode-zellij.schema.json",
79
+ "autoUpdate": true,
80
+ "pty": {
81
+ "enabled": true,
82
+ "sudoPane": "allow" // allow, deny, or hide
83
+ },
84
+ "tabTitle": { "enabled": true }
85
+ }
86
+ ```
87
+
88
+ 將 `autoUpdate`、`pty.enabled` 或 `tabTitle.enabled` 設為 `false` 可關閉對應功能。
89
+
72
90
  ## 動態 tab title
73
91
 
74
92
  當 OpenCode 在 Zellij 中執行時,plugin 會更新目前 tab title,顯示 project、branch 與目前 OpenCode 狀態:
package/dist/index.mjs CHANGED
@@ -18,6 +18,11 @@ function debug(message, ...details) {
18
18
  console.warn(`[opencode-zellij] ${message}`, ...details);
19
19
  }
20
20
  //#endregion
21
+ //#region src/utils/errors.ts
22
+ function errorMessage(error) {
23
+ return error instanceof Error ? error.message : String(error);
24
+ }
25
+ //#endregion
21
26
  //#region src/auto-update.ts
22
27
  const PACKAGE_NAME = "opencode-zellij";
23
28
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/-/package/opencode-zellij/dist-tags";
@@ -39,7 +44,9 @@ async function installedPackageMetadata(installRoot) {
39
44
  version: typeof pkg.version === "string" ? pkg.version : void 0,
40
45
  main: typeof pkg.main === "string" ? pkg.main : void 0
41
46
  };
42
- } catch {}
47
+ } catch (error) {
48
+ debug("installedPackageMetadata failed", errorMessage(error));
49
+ }
43
50
  }
44
51
  function isExpectedPackage(metadata, version) {
45
52
  return metadata?.name === "opencode-zellij" && metadata.version === version;
@@ -108,7 +115,9 @@ async function findInstallContext(importMetaUrl) {
108
115
  currentVersion: pkg.version
109
116
  };
110
117
  }
111
- } catch {}
118
+ } catch (error) {
119
+ debug("findInstallContext package.json read failed", errorMessage(error));
120
+ }
112
121
  }
113
122
  const parent = dirname(dir);
114
123
  if (parent === dir) break;
@@ -254,7 +263,7 @@ const ptyLayerSchema = z.object({
254
263
  enabled: z.boolean().optional().describe("Enable Zellij-backed PTY tools."),
255
264
  sudoPane: sudoPaneSchema.optional().describe("Controls whether the sudo pane tool is available, denied, or hidden.")
256
265
  }).strict();
257
- const autoUpdateLayerSchema = z.object({ enabled: z.boolean().optional().describe("Enable automatic update checks for the opencode-zellij plugin.") }).strict();
266
+ const autoUpdateLayerSchema = z.boolean().optional().describe("Enable automatic update checks for the opencode-zellij plugin.");
258
267
  const sidecarConfigSchema = z.object({
259
268
  $schema: z.string().optional().describe("JSON Schema URI for editor completion."),
260
269
  tabTitle: tabTitleLayerSchema.optional(),
@@ -274,7 +283,7 @@ const defaultConfig = {
274
283
  enabled: true,
275
284
  sudoPane: "allow"
276
285
  },
277
- autoUpdate: { enabled: true }
286
+ autoUpdate: true
278
287
  };
279
288
  function validConfigLayer(value) {
280
289
  const result = sidecarConfigSchema.safeParse(value);
@@ -299,7 +308,7 @@ function mergeConfig(user, project) {
299
308
  enabled: project?.pty?.enabled ?? user?.pty?.enabled ?? defaultConfig.pty.enabled,
300
309
  sudoPane: project?.pty?.sudoPane ?? user?.pty?.sudoPane ?? defaultConfig.pty.sudoPane
301
310
  },
302
- autoUpdate: { enabled: project?.autoUpdate?.enabled ?? user?.autoUpdate?.enabled ?? defaultConfig.autoUpdate.enabled }
311
+ autoUpdate: project?.autoUpdate ?? user?.autoUpdate ?? defaultConfig.autoUpdate
303
312
  };
304
313
  }
305
314
  async function loadConfigLayer(directory, warnings) {
@@ -479,47 +488,7 @@ function buildCommandArgv(input, options = {}) {
479
488
  ];
480
489
  }
481
490
  //#endregion
482
- //#region src/zellij/cli.ts
483
- const execFileAsync$1 = promisify(execFile);
484
- function zellijCommandArgs(actionArgs) {
485
- const sessionName = process.env.ZELLIJ_SESSION_NAME?.trim();
486
- if (sessionName) return [
487
- "--session",
488
- sessionName,
489
- ...actionArgs
490
- ];
491
- return actionArgs;
492
- }
493
- function zellijActionArgs(action, args = []) {
494
- return [
495
- "action",
496
- action,
497
- ...args
498
- ];
499
- }
500
- function buildNewPaneActionArgs(options) {
501
- const args = ["action", "new-pane"];
502
- if (process.env.ZELLIJ) args.push("--near-current-pane");
503
- if (options.title) args.push("--name", options.title);
504
- if (options.cwd) args.push("--cwd", options.cwd);
505
- if (options.floating) args.push("--floating");
506
- args.push("--", ...buildCommandArgv(options, { exitCodeToken: options.exitCodeToken }));
507
- return args;
508
- }
509
- function buildRenameTabActionArgs(title, options = {}) {
510
- if (options.tabId !== void 0) return [
511
- "action",
512
- "rename-tab",
513
- "--tab-id",
514
- String(options.tabId),
515
- title
516
- ];
517
- return [
518
- "action",
519
- "rename-tab",
520
- title
521
- ];
522
- }
491
+ //#region src/zellij/parse.ts
523
492
  function numericProperty(object, keys) {
524
493
  for (const key of keys) {
525
494
  const value = object[key];
@@ -530,6 +499,12 @@ function numericProperty(object, keys) {
530
499
  }
531
500
  }
532
501
  }
502
+ function stringProperty$1(object, keys) {
503
+ for (const key of keys) {
504
+ const value = object[key];
505
+ if (typeof value === "string") return value;
506
+ }
507
+ }
533
508
  function paneMatches(object, paneId) {
534
509
  return numericProperty(object, [
535
510
  "id",
@@ -559,10 +534,84 @@ function parseCurrentPaneTabId(listPanesJson, paneId) {
559
534
  if (!Number.isInteger(parsedPaneId)) return void 0;
560
535
  try {
561
536
  return findPaneTabId(JSON.parse(listPanesJson), parsedPaneId);
562
- } catch {
537
+ } catch (error) {
538
+ debug("parseCurrentPaneTabId failed", errorMessage(error));
563
539
  return;
564
540
  }
565
541
  }
542
+ function tabNameProperty(object, tabId) {
543
+ if (tabId === void 0) return void 0;
544
+ if (numericProperty(object, ["tab_id", "tabId"]) !== tabId) return void 0;
545
+ const name = stringProperty$1(object, ["name", "title"]);
546
+ return typeof name === "string" ? name : void 0;
547
+ }
548
+ function findTabName(value, tabId) {
549
+ if (Array.isArray(value)) {
550
+ for (const item of value) {
551
+ const found = findTabName(item, tabId);
552
+ if (found !== void 0) return found;
553
+ }
554
+ return;
555
+ }
556
+ if (typeof value !== "object" || value === null) return void 0;
557
+ const object = value;
558
+ const name = tabNameProperty(object, tabId);
559
+ if (name !== void 0) return name;
560
+ for (const nested of Object.values(object)) {
561
+ const found = findTabName(nested, tabId);
562
+ if (found !== void 0) return found;
563
+ }
564
+ }
565
+ function parseTabName(listTabsJson, tabId) {
566
+ try {
567
+ return findTabName(JSON.parse(listTabsJson), tabId);
568
+ } catch (error) {
569
+ debug("parseTabName failed", errorMessage(error));
570
+ return;
571
+ }
572
+ }
573
+ //#endregion
574
+ //#region src/zellij/cli.ts
575
+ const execFileAsync$1 = promisify(execFile);
576
+ function zellijCommandArgs(actionArgs) {
577
+ const sessionName = process.env.ZELLIJ_SESSION_NAME?.trim();
578
+ if (sessionName) return [
579
+ "--session",
580
+ sessionName,
581
+ ...actionArgs
582
+ ];
583
+ return actionArgs;
584
+ }
585
+ function zellijActionArgs(action, args = []) {
586
+ return [
587
+ "action",
588
+ action,
589
+ ...args
590
+ ];
591
+ }
592
+ function buildNewPaneActionArgs(options) {
593
+ const args = ["action", "new-pane"];
594
+ if (process.env.ZELLIJ) args.push("--near-current-pane");
595
+ if (options.title) args.push("--name", options.title);
596
+ if (options.cwd) args.push("--cwd", options.cwd);
597
+ if (options.floating) args.push("--floating");
598
+ args.push("--", ...buildCommandArgv(options, { exitCodeToken: options.exitCodeToken }));
599
+ return args;
600
+ }
601
+ function buildRenameTabActionArgs(title, options = {}) {
602
+ if (options.tabId !== void 0) return [
603
+ "action",
604
+ "rename-tab",
605
+ "--tab-id",
606
+ String(options.tabId),
607
+ title
608
+ ];
609
+ return [
610
+ "action",
611
+ "rename-tab",
612
+ title
613
+ ];
614
+ }
566
615
  function ensureZellijTarget() {
567
616
  if (process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) return;
568
617
  throw new Error("Zellij context not found. Run OpenCode inside Zellij or set ZELLIJ_SESSION_NAME to an existing session.");
@@ -636,6 +685,12 @@ var ZellijCli = class {
636
685
  if (tabId === void 0 && process.env.ZELLIJ) throw new Error(`Could not resolve Zellij tab id for pane ${process.env.ZELLIJ_PANE_ID ?? "<missing>"}`);
637
686
  await runZellij(tabId === void 0 ? buildRenameTabActionArgs(title) : buildRenameTabActionArgs(title, { tabId }));
638
687
  }
688
+ async currentTabTitle() {
689
+ if (!process.env.ZELLIJ_PANE_ID) return void 0;
690
+ const tabId = await this.currentPaneTabId();
691
+ if (tabId === void 0) return void 0;
692
+ return parseTabName((await runZellij(zellijActionArgs("list-tabs", ["--json"]), { timeoutMs: 5e3 })).stdout, tabId);
693
+ }
639
694
  };
640
695
  const zellijCli = new ZellijCli();
641
696
  //#endregion
@@ -656,7 +711,8 @@ function parseLinuxProcessStartTime(stat) {
656
711
  function linuxProcessStartTime(pid) {
657
712
  try {
658
713
  return parseLinuxProcessStartTime(readFileSync(`/proc/${pid}/stat`, "utf8"));
659
- } catch {
714
+ } catch (error) {
715
+ debug("linuxProcessStartTime failed", errorMessage(error));
660
716
  return null;
661
717
  }
662
718
  }
@@ -677,7 +733,8 @@ function readRegistry() {
677
733
  const parsed = JSON.parse(readFileSync(file, "utf8"));
678
734
  if (parsed.version !== 1 || parsed.instanceId !== instanceId || parsed.ownerPid !== process.pid || !Array.isArray(parsed.panes)) return emptyRegistry();
679
735
  return parsed;
680
- } catch {
736
+ } catch (error) {
737
+ debug("readRegistry failed", errorMessage(error));
681
738
  return emptyRegistry();
682
739
  }
683
740
  }
@@ -725,7 +782,8 @@ function cleanupStaleWatchdogRegistries() {
725
782
  if (registry.version !== 1 || ownerStillMatches(registry)) continue;
726
783
  closeRegistryPanes(registry);
727
784
  rmSync(file, { force: true });
728
- } catch {
785
+ } catch (error) {
786
+ debug("cleanupStaleWatchdogRegistries failed", errorMessage(error));
729
787
  rmSync(file, { force: true });
730
788
  }
731
789
  }
@@ -733,7 +791,8 @@ function cleanupStaleWatchdogRegistries() {
733
791
  function ownerStillMatches(registry) {
734
792
  try {
735
793
  process.kill(registry.ownerPid, 0);
736
- } catch {
794
+ } catch (error) {
795
+ debug("ownerStillMatches kill check failed", errorMessage(error));
737
796
  return false;
738
797
  }
739
798
  return !registry.ownerStartTime || linuxProcessStartTime(registry.ownerPid) === registry.ownerStartTime;
@@ -785,7 +844,9 @@ function unregisterPaneFromWatchdog(sessionId) {
785
844
  function removeWatchdogRegistry() {
786
845
  try {
787
846
  rmSync(watchdogRegistryPath(), { force: true });
788
- } catch {}
847
+ } catch (error) {
848
+ debug("removeWatchdogRegistry failed", errorMessage(error));
849
+ }
789
850
  if (!watchdogChild) watchdogStarted = false;
790
851
  }
791
852
  //#endregion
@@ -959,14 +1020,7 @@ var SubscriberManager = class {
959
1020
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
960
1021
  lastExitedAt: null
961
1022
  };
962
- if (!existing) {
963
- this.subscribers.set(session.id, state);
964
- try {
965
- state.buffer.appendSnapshot(await zellijCli.dumpScreen(session.paneId));
966
- this.sessions.updateLineCount(session.id, state.buffer.lineCount);
967
- } catch {}
968
- if (this.subscribers.get(session.id) !== state) return;
969
- }
1023
+ if (!existing) this.subscribers.set(session.id, state);
970
1024
  const child = spawn("zellij", zellijCommandArgs([
971
1025
  "subscribe",
972
1026
  "--pane-id",
@@ -993,6 +1047,14 @@ var SubscriberManager = class {
993
1047
  child.stderr.on("data", (chunk) => this.handleStderr(session.id, child, chunk));
994
1048
  child.on("exit", () => this.handleSubscriberExit(session.id, child));
995
1049
  child.on("error", (error) => this.handleSubscriberError(session.id, child, error));
1050
+ if (!existing) try {
1051
+ const snapshot = await zellijCli.dumpScreen(session.paneId);
1052
+ if (this.subscribers.get(session.id) !== state || state.child !== child) return;
1053
+ state.buffer.appendSnapshot(snapshot);
1054
+ this.sessions.updateLineCount(session.id, state.buffer.lineCount);
1055
+ } catch (error) {
1056
+ debug("dumpScreen failed", errorMessage(error));
1057
+ }
996
1058
  }
997
1059
  read(sessionId, input) {
998
1060
  const state = this.subscribers.get(sessionId);
@@ -1032,7 +1094,9 @@ var SubscriberManager = class {
1032
1094
  this.stop(sessionId);
1033
1095
  try {
1034
1096
  await zellijCli.closePane(session.paneId);
1035
- } catch {}
1097
+ } catch (error) {
1098
+ debug("closePane failed", errorMessage(error));
1099
+ }
1036
1100
  }
1037
1101
  handleStdout(sessionId, child, chunk) {
1038
1102
  const state = this.subscribers.get(sessionId);
@@ -1051,16 +1115,18 @@ var SubscriberManager = class {
1051
1115
  const parsed = JSON.parse(trimmed);
1052
1116
  if (!parsed || typeof parsed !== "object") return;
1053
1117
  event = parsed;
1054
- } catch {
1118
+ } catch (error) {
1055
1119
  state.buffer.append(trimmed);
1056
1120
  this.sessions.updateLineCount(sessionId, state.buffer.lineCount);
1121
+ debug("JSON parse of subscriber event failed, treating as raw text", errorMessage(error));
1057
1122
  return;
1058
1123
  }
1059
1124
  let session;
1060
1125
  try {
1061
1126
  session = this.sessions.get(sessionId);
1062
- } catch {
1127
+ } catch (error) {
1063
1128
  this.forget(sessionId);
1129
+ debug("session lookup by id failed", errorMessage(error));
1064
1130
  return;
1065
1131
  }
1066
1132
  const paneId = eventPaneId(event);
@@ -1146,11 +1212,6 @@ function jsonResponse(value) {
1146
1212
  return JSON.stringify(value, null, 2);
1147
1213
  }
1148
1214
  //#endregion
1149
- //#region src/utils/errors.ts
1150
- function errorMessage(error) {
1151
- return error instanceof Error ? error.message : String(error);
1152
- }
1153
- //#endregion
1154
1215
  //#region src/tools/output.ts
1155
1216
  function emptyOutputSnapshot(lineCount = 0) {
1156
1217
  return {
@@ -1609,11 +1670,15 @@ function cleanupPanesOnShutdown(sessions = sessionManager, subscribers = subscri
1609
1670
  for (const session of sessions.list()) {
1610
1671
  try {
1611
1672
  zellijCli.closePaneSync(session.paneId);
1612
- } catch {}
1673
+ } catch (error) {
1674
+ debug("cleanupPanesOnShutdown closePane failed", errorMessage(error));
1675
+ }
1613
1676
  subscribers.forget(session.id);
1614
1677
  try {
1615
1678
  sessions.remove(session.id);
1616
- } catch {}
1679
+ } catch (error) {
1680
+ debug("cleanupPanesOnShutdown sessions.remove failed", errorMessage(error));
1681
+ }
1617
1682
  }
1618
1683
  }
1619
1684
  function registerShutdownCleanup() {
@@ -1683,7 +1748,8 @@ async function readGitBranch(worktree) {
1683
1748
  async function getInitialBranch(worktree, readBranch = readGitBranch) {
1684
1749
  try {
1685
1750
  return (await readBranch(worktree)).trim() || void 0;
1686
- } catch {
1751
+ } catch (error) {
1752
+ debug("getInitialBranch failed", errorMessage(error));
1687
1753
  return;
1688
1754
  }
1689
1755
  }
@@ -1691,6 +1757,7 @@ function shouldReadInitialBranch(zellij) {
1691
1757
  return Boolean(zellij);
1692
1758
  }
1693
1759
  function handleTabTitleEvent(tabTitleManager, event) {
1760
+ if (event.type === "server.instance.disposed" || event.type === "global.disposed") return tabTitleManager.destroy?.();
1694
1761
  if (!isRecord(event.properties)) return;
1695
1762
  const properties = event.properties;
1696
1763
  switch (event.type) {
@@ -1740,10 +1807,6 @@ function handleTabTitleEvent(tabTitleManager, event) {
1740
1807
  if (sessionID) tabTitleManager.removeSession(sessionID);
1741
1808
  break;
1742
1809
  }
1743
- case "server.instance.disposed":
1744
- case "global.disposed":
1745
- tabTitleManager.destroy?.();
1746
- break;
1747
1810
  }
1748
1811
  }
1749
1812
  //#endregion
@@ -1774,6 +1837,7 @@ var TabTitleManager = class {
1774
1837
  retryTimer;
1775
1838
  retryAttempt = 0;
1776
1839
  syncInFlight = false;
1840
+ syncPromise;
1777
1841
  debounceMs;
1778
1842
  retryInitialMs;
1779
1843
  retryMaxMs;
@@ -1782,6 +1846,10 @@ var TabTitleManager = class {
1782
1846
  emojis;
1783
1847
  enabled;
1784
1848
  destroyed = false;
1849
+ originalTabTitle;
1850
+ originalTabTitleLoaded = false;
1851
+ originalTabTitlePromise;
1852
+ destroyPromise;
1785
1853
  constructor(options) {
1786
1854
  this.projectName = options.projectName;
1787
1855
  this.branchName = options.branchName?.trim() || void 0;
@@ -1854,6 +1922,8 @@ var TabTitleManager = class {
1854
1922
  }
1855
1923
  async renderImmediate() {
1856
1924
  if (!this.enabled || this.destroyed) return;
1925
+ await this.ensureOriginalTabTitle();
1926
+ if (this.destroyed) return;
1857
1927
  this.desiredTitle = this.buildTitle();
1858
1928
  this.clearDebounceTimer();
1859
1929
  await this.syncDesiredTitle();
@@ -1868,14 +1938,20 @@ var TabTitleManager = class {
1868
1938
  this.clearDebounceTimer();
1869
1939
  this.debounceTimer = setTimeout(() => {
1870
1940
  this.debounceTimer = void 0;
1871
- this.syncDesiredTitle().catch(() => {});
1941
+ this.syncDesiredTitle().catch((error) => debug("debounced tab title sync failed", errorMessage(error)));
1872
1942
  }, this.debounceMs);
1873
1943
  this.unrefTimer(this.debounceTimer);
1874
1944
  }
1875
1945
  async syncDesiredTitle() {
1876
1946
  if (!this.enabled || this.destroyed) return;
1877
- if (this.syncInFlight) return;
1947
+ await this.ensureOriginalTabTitle();
1948
+ if (this.destroyed) return;
1949
+ if (this.syncInFlight) return this.syncPromise;
1878
1950
  this.syncInFlight = true;
1951
+ this.syncPromise = this.runTitleSync();
1952
+ return this.syncPromise;
1953
+ }
1954
+ async runTitleSync() {
1879
1955
  try {
1880
1956
  while (this.desiredTitle && this.desiredTitle !== this.lastSyncedTitle) {
1881
1957
  const title = this.desiredTitle;
@@ -1892,6 +1968,7 @@ var TabTitleManager = class {
1892
1968
  }
1893
1969
  } finally {
1894
1970
  this.syncInFlight = false;
1971
+ this.syncPromise = void 0;
1895
1972
  }
1896
1973
  }
1897
1974
  scheduleRetry() {
@@ -1900,7 +1977,7 @@ var TabTitleManager = class {
1900
1977
  this.retryAttempt += 1;
1901
1978
  this.retryTimer = setTimeout(() => {
1902
1979
  this.retryTimer = void 0;
1903
- this.syncDesiredTitle().catch(() => {});
1980
+ this.syncDesiredTitle().catch((error) => debug("retry tab title sync failed", errorMessage(error)));
1904
1981
  }, delay);
1905
1982
  this.unrefTimer(this.retryTimer);
1906
1983
  }
@@ -1915,10 +1992,39 @@ var TabTitleManager = class {
1915
1992
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
1916
1993
  this.debounceTimer = void 0;
1917
1994
  }
1995
+ async ensureOriginalTabTitle() {
1996
+ if (!this.enabled || this.originalTabTitleLoaded) return;
1997
+ if (this.originalTabTitlePromise) return this.originalTabTitlePromise;
1998
+ this.originalTabTitlePromise = this.saveOriginalTabTitle();
1999
+ return this.originalTabTitlePromise;
2000
+ }
2001
+ async saveOriginalTabTitle() {
2002
+ try {
2003
+ const title = await this.cli.currentTabTitle();
2004
+ if (title !== void 0) this.originalTabTitle = title;
2005
+ } catch (error) {
2006
+ debug("TabTitleManager failed to save original tab title", errorMessage(error));
2007
+ } finally {
2008
+ this.originalTabTitleLoaded = true;
2009
+ this.originalTabTitlePromise = void 0;
2010
+ }
2011
+ }
1918
2012
  destroy() {
2013
+ if (this.destroyed) return this.destroyPromise ?? Promise.resolve();
1919
2014
  this.destroyed = true;
1920
2015
  this.clearDebounceTimer();
1921
2016
  this.clearRetryTimer();
2017
+ if (!this.enabled) return Promise.resolve();
2018
+ this.destroyPromise = this.restoreOriginalTabTitle().catch((error) => debug("TabTitleManager failed to restore original tab title", errorMessage(error)));
2019
+ return this.destroyPromise;
2020
+ }
2021
+ async restoreOriginalTabTitle() {
2022
+ await this.originalTabTitlePromise;
2023
+ await this.syncPromise;
2024
+ const originalTitle = this.originalTabTitle;
2025
+ this.originalTabTitle = void 0;
2026
+ if (originalTitle === void 0) return;
2027
+ await this.cli.renameTab(originalTitle);
1922
2028
  }
1923
2029
  };
1924
2030
  //#endregion
@@ -1942,13 +2048,13 @@ function showUpdateToast(client, result) {
1942
2048
  message: `Updated to ${result.toVersion}. Restart OpenCode to apply the changes.`,
1943
2049
  variant: "success",
1944
2050
  duration: 1e4
1945
- } }).catch(() => {});
2051
+ } }).catch((error) => debug("show update toast for successful update failed", errorMessage(error)));
1946
2052
  else if (result.type === "failed") client.tui.showToast({ body: {
1947
2053
  title: "opencode-zellij update failed",
1948
2054
  message: `Failed to update to ${result.latestVersion}.`,
1949
2055
  variant: "error",
1950
2056
  duration: 8e3
1951
- } }).catch(() => {});
2057
+ } }).catch((error) => debug("show update toast for failed update failed", errorMessage(error)));
1952
2058
  }
1953
2059
  function startAutoUpdateCheck(client, importMetaUrl, check = checkAndUpdate) {
1954
2060
  (async () => {
@@ -1993,13 +2099,13 @@ function createZellijPtyPlugin(dependencies = {}) {
1993
2099
  branch: config.tabTitle.emojiBranch
1994
2100
  }
1995
2101
  }) : void 0;
1996
- tabTitleManager?.renderImmediate().catch(() => {});
2102
+ tabTitleManager?.renderImmediate().catch((error) => debug("initial tab title render failed", errorMessage(error)));
1997
2103
  const client = input.client;
1998
- if (config.autoUpdate.enabled) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
2104
+ if (config.autoUpdate) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
1999
2105
  return {
2000
2106
  async event(input) {
2001
2107
  const event = input.event;
2002
- if (tabTitleManager) handleTabTitleEvent(tabTitleManager, event);
2108
+ if (tabTitleManager) await handleTabTitleEvent(tabTitleManager, event);
2003
2109
  if (event.type === "session.deleted") {
2004
2110
  const sessionID = deletedSessionID(event);
2005
2111
  if (!sessionID) return;