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 +21 -0
- package/README.md +18 -0
- package/README.zh.md +18 -0
- package/dist/index.mjs +189 -83
- package/dist/index.mjs.map +1 -1
- package/dist/pane-watchdog-runner.mjs +13 -4
- package/dist/pane-watchdog-runner.mjs.map +1 -1
- package/package.json +2 -1
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.
|
|
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:
|
|
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:
|
|
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/
|
|
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
|
-
|
|
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
|
|
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;
|