opencode-zellij 0.0.1 → 0.0.3
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/README.md +39 -2
- package/README.zh.md +39 -2
- package/dist/index.mjs +620 -29
- package/dist/index.mjs.map +1 -1
- package/dist/pane-watchdog-runner.d.mts +1 -0
- package/dist/pane-watchdog-runner.mjs +69 -0
- package/dist/pane-watchdog-runner.mjs.map +1 -0
- package/package.json +13 -5
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,131 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import path, { join } from "node:path";
|
|
6
|
+
import { parseJSON, parseJSONC } from "confbox";
|
|
7
|
+
import { z } from "zod";
|
|
1
8
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { setTimeout } from "node:timers/promises";
|
|
9
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
3
10
|
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
import { execFile, spawn } from "node:child_process";
|
|
5
|
-
import process from "node:process";
|
|
11
|
+
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
6
12
|
import { promisify } from "node:util";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
7
14
|
import { Buffer } from "node:buffer";
|
|
15
|
+
//#region src/config.ts
|
|
16
|
+
const sudoPaneSchema = z.enum([
|
|
17
|
+
"allow",
|
|
18
|
+
"deny",
|
|
19
|
+
"hide"
|
|
20
|
+
]);
|
|
21
|
+
const configFilenames = ["opencode-zellij.config.jsonc", "opencode-zellij.config.json"];
|
|
22
|
+
const tabTitleLayerSchema = z.object({
|
|
23
|
+
enabled: z.boolean().optional().describe("Enable dynamic Zellij tab title updates."),
|
|
24
|
+
emojiIdle: z.string().optional().describe("Prefix used when OpenCode is idle."),
|
|
25
|
+
emojiRunning: z.string().optional().describe("Prefix used while OpenCode is running work."),
|
|
26
|
+
emojiNeedsInput: z.string().optional().describe("Prefix used when OpenCode is waiting for human input."),
|
|
27
|
+
emojiBranch: z.string().optional().describe("Prefix used before the current git branch name."),
|
|
28
|
+
debounceMs: z.number().finite().min(0).optional().describe("Debounce time for tab title updates in milliseconds.")
|
|
29
|
+
}).strict();
|
|
30
|
+
const ptyLayerSchema = z.object({
|
|
31
|
+
enabled: z.boolean().optional().describe("Enable Zellij-backed PTY tools."),
|
|
32
|
+
sudoPane: sudoPaneSchema.optional().describe("Controls whether the sudo pane tool is available, denied, or hidden.")
|
|
33
|
+
}).strict();
|
|
34
|
+
const sidecarConfigSchema = z.object({
|
|
35
|
+
$schema: z.string().optional().describe("JSON Schema URI for editor completion."),
|
|
36
|
+
tabTitle: tabTitleLayerSchema.optional(),
|
|
37
|
+
pty: ptyLayerSchema.optional()
|
|
38
|
+
}).strict();
|
|
39
|
+
const defaultConfig = {
|
|
40
|
+
tabTitle: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
emojiIdle: "🟢",
|
|
43
|
+
emojiRunning: "⚡",
|
|
44
|
+
emojiNeedsInput: "💬",
|
|
45
|
+
emojiBranch: "🌱",
|
|
46
|
+
debounceMs: 300
|
|
47
|
+
},
|
|
48
|
+
pty: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
sudoPane: "allow"
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function validConfigLayer(value) {
|
|
54
|
+
const result = sidecarConfigSchema.safeParse(value);
|
|
55
|
+
if (!result.success) return void 0;
|
|
56
|
+
return {
|
|
57
|
+
tabTitle: result.data.tabTitle,
|
|
58
|
+
pty: result.data.pty
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function mergeConfig(user, project) {
|
|
62
|
+
return {
|
|
63
|
+
tabTitle: {
|
|
64
|
+
enabled: project?.tabTitle?.enabled ?? user?.tabTitle?.enabled ?? defaultConfig.tabTitle.enabled,
|
|
65
|
+
emojiIdle: project?.tabTitle?.emojiIdle ?? user?.tabTitle?.emojiIdle ?? defaultConfig.tabTitle.emojiIdle,
|
|
66
|
+
emojiRunning: project?.tabTitle?.emojiRunning ?? user?.tabTitle?.emojiRunning ?? defaultConfig.tabTitle.emojiRunning,
|
|
67
|
+
emojiNeedsInput: project?.tabTitle?.emojiNeedsInput ?? user?.tabTitle?.emojiNeedsInput ?? defaultConfig.tabTitle.emojiNeedsInput,
|
|
68
|
+
emojiBranch: project?.tabTitle?.emojiBranch ?? user?.tabTitle?.emojiBranch ?? defaultConfig.tabTitle.emojiBranch,
|
|
69
|
+
debounceMs: project?.tabTitle?.debounceMs ?? user?.tabTitle?.debounceMs ?? defaultConfig.tabTitle.debounceMs
|
|
70
|
+
},
|
|
71
|
+
pty: {
|
|
72
|
+
enabled: project?.pty?.enabled ?? user?.pty?.enabled ?? defaultConfig.pty.enabled,
|
|
73
|
+
sudoPane: project?.pty?.sudoPane ?? user?.pty?.sudoPane ?? defaultConfig.pty.sudoPane
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function loadConfigLayer(directory, warnings) {
|
|
78
|
+
const configFile = detectConfigFile(directory);
|
|
79
|
+
if (!configFile) return {};
|
|
80
|
+
try {
|
|
81
|
+
const text = await readFile(configFile, "utf8");
|
|
82
|
+
const layer = validConfigLayer(configFile.endsWith(".jsonc") ? parseJSONC(text) : parseJSON(text));
|
|
83
|
+
if (!layer) {
|
|
84
|
+
warnings.push(`Ignoring invalid config shape in ${configFile}.`);
|
|
85
|
+
return { source: configFile };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
layer,
|
|
89
|
+
source: configFile
|
|
90
|
+
};
|
|
91
|
+
} catch (cause) {
|
|
92
|
+
warnings.push(`Ignoring unreadable or invalid config file ${configFile}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function detectConfigFile(directory) {
|
|
97
|
+
return configFilenames.map((filename) => join(directory, filename)).find((path) => existsSync(path));
|
|
98
|
+
}
|
|
99
|
+
function userConfigDir() {
|
|
100
|
+
return process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, "opencode") : join(homedir(), ".config", "opencode");
|
|
101
|
+
}
|
|
102
|
+
function projectConfigDirs(input) {
|
|
103
|
+
const dirs = [];
|
|
104
|
+
if (input.worktree) dirs.push(join(input.worktree, ".opencode"));
|
|
105
|
+
if (input.directory && input.directory !== input.worktree) dirs.push(join(input.directory, ".opencode"));
|
|
106
|
+
return dirs;
|
|
107
|
+
}
|
|
108
|
+
async function loadConfig(input) {
|
|
109
|
+
const warnings = [];
|
|
110
|
+
const sources = {};
|
|
111
|
+
const userResult = await loadConfigLayer(userConfigDir(), warnings);
|
|
112
|
+
const userLayer = userResult.layer;
|
|
113
|
+
if (userResult.source && userLayer) sources.user = userResult.source;
|
|
114
|
+
let projectLayer;
|
|
115
|
+
for (const projectDir of projectConfigDirs(input)) {
|
|
116
|
+
const projectResult = await loadConfigLayer(projectDir, warnings);
|
|
117
|
+
if (!projectResult.source) continue;
|
|
118
|
+
projectLayer = projectResult.layer;
|
|
119
|
+
if (projectLayer) sources.project = projectResult.source;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
config: mergeConfig(userLayer, projectLayer),
|
|
124
|
+
sources,
|
|
125
|
+
warnings
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
8
129
|
//#region src/utils/shell-args.ts
|
|
9
130
|
const directCommandExitWrapper = "token=\"$1\"; shift; set +e; \"$@\"; code=$?; printf \"\\n[zellij-pty:%s] exit-code=%s\\n\" \"$token\" \"$code\"; exit \"$code\"";
|
|
10
131
|
const shellCommandExitWrapper = "token=\"$1\"; command=\"$2\"; set +e; bash -lc \"$command\"; code=$?; printf \"\\n[zellij-pty:%s] exit-code=%s\\n\" \"$token\" \"$code\"; exit \"$code\"";
|
|
@@ -63,6 +184,9 @@ function wildcardMatches(pattern, commandLine) {
|
|
|
63
184
|
return new RegExp(`^${pattern.split("*").map(escapeRegex).join(".*")}$`).test(commandLine);
|
|
64
185
|
}
|
|
65
186
|
function configurePolicy(config) {
|
|
187
|
+
configuredDenyCommands = [];
|
|
188
|
+
configuredAllowCommands = [];
|
|
189
|
+
allowSudoPane = true;
|
|
66
190
|
if (!config || typeof config !== "object") return;
|
|
67
191
|
const object = config;
|
|
68
192
|
if (isStringArray(object.denyCommands)) configuredDenyCommands = object.denyCommands;
|
|
@@ -74,8 +198,8 @@ function assertCommandAllowed(input) {
|
|
|
74
198
|
for (const pattern of denyPatterns) if (pattern.test(commandLine)) throw new Error(`Command denied by zellij-pty policy: ${commandLine}`);
|
|
75
199
|
for (const pattern of configuredDenyCommands) if (wildcardMatches(pattern, commandLine)) throw new Error(`Command denied by zellij-pty configured deny rule: ${commandLine}`);
|
|
76
200
|
if (configuredAllowCommands.length > 0 && !configuredAllowCommands.some((pattern) => wildcardMatches(pattern, commandLine))) throw new Error(`Command denied by zellij-pty allow list: ${commandLine}`);
|
|
77
|
-
if (!input.humanInputOnly && sudoPattern.test(commandLine)) throw new Error("sudo commands must use
|
|
78
|
-
if (input.humanInputOnly &&
|
|
201
|
+
if (!input.humanInputOnly && sudoPattern.test(commandLine)) throw new Error("sudo commands must use zellij_pty_request_sudo so credentials stay human-input-only and never pass through agent tool input.");
|
|
202
|
+
if (input.humanInputOnly && !allowSudoPane) throw new Error("sudo pane is disabled by zellij-pty policy.");
|
|
79
203
|
}
|
|
80
204
|
//#endregion
|
|
81
205
|
//#region src/utils/ids.ts
|
|
@@ -159,7 +283,7 @@ var SessionManager = class {
|
|
|
159
283
|
const sessionManager = new SessionManager();
|
|
160
284
|
//#endregion
|
|
161
285
|
//#region src/zellij/cli.ts
|
|
162
|
-
const execFileAsync = promisify(execFile);
|
|
286
|
+
const execFileAsync$1 = promisify(execFile);
|
|
163
287
|
function zellijCommandArgs(actionArgs) {
|
|
164
288
|
const sessionName = process.env.ZELLIJ_SESSION_NAME?.trim();
|
|
165
289
|
if (sessionName) return [
|
|
@@ -185,6 +309,13 @@ function buildNewPaneActionArgs(options) {
|
|
|
185
309
|
args.push("--", ...buildCommandArgv(options, { exitCodeToken: options.exitCodeToken }));
|
|
186
310
|
return args;
|
|
187
311
|
}
|
|
312
|
+
function buildRenameTabActionArgs(title) {
|
|
313
|
+
return [
|
|
314
|
+
"action",
|
|
315
|
+
"rename-tab",
|
|
316
|
+
title
|
|
317
|
+
];
|
|
318
|
+
}
|
|
188
319
|
function ensureZellijTarget() {
|
|
189
320
|
if (process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) return;
|
|
190
321
|
throw new Error("Zellij context not found. Run OpenCode inside Zellij or set ZELLIJ_SESSION_NAME to an existing session.");
|
|
@@ -192,7 +323,7 @@ function ensureZellijTarget() {
|
|
|
192
323
|
async function runZellij(actionArgs, options = {}) {
|
|
193
324
|
ensureZellijTarget();
|
|
194
325
|
try {
|
|
195
|
-
const result = await execFileAsync("zellij", zellijCommandArgs(actionArgs), {
|
|
326
|
+
const result = await execFileAsync$1("zellij", zellijCommandArgs(actionArgs), {
|
|
196
327
|
encoding: "utf8",
|
|
197
328
|
timeout: options.timeoutMs ?? 1e4,
|
|
198
329
|
maxBuffer: 20 * 1024 * 1024
|
|
@@ -230,6 +361,14 @@ var ZellijCli = class {
|
|
|
230
361
|
async closePane(paneId) {
|
|
231
362
|
await runZellij(zellijActionArgs("close-pane", ["--pane-id", paneId]));
|
|
232
363
|
}
|
|
364
|
+
closePaneSync(paneId) {
|
|
365
|
+
ensureZellijTarget();
|
|
366
|
+
spawnSync("zellij", zellijCommandArgs(zellijActionArgs("close-pane", ["--pane-id", paneId])), {
|
|
367
|
+
encoding: "utf8",
|
|
368
|
+
stdio: "ignore",
|
|
369
|
+
timeout: 2e3
|
|
370
|
+
});
|
|
371
|
+
}
|
|
233
372
|
async focusPane(paneId) {
|
|
234
373
|
await runZellij(zellijActionArgs("focus-pane-id", [paneId]));
|
|
235
374
|
}
|
|
@@ -240,9 +379,149 @@ var ZellijCli = class {
|
|
|
240
379
|
"--full"
|
|
241
380
|
]), { timeoutMs: 1e4 })).stdout;
|
|
242
381
|
}
|
|
382
|
+
async renameTab(title) {
|
|
383
|
+
await runZellij(buildRenameTabActionArgs(title));
|
|
384
|
+
}
|
|
243
385
|
};
|
|
244
386
|
const zellijCli = new ZellijCli();
|
|
245
387
|
//#endregion
|
|
388
|
+
//#region src/zellij/pane-watchdog.ts
|
|
389
|
+
const instanceId = randomUUID();
|
|
390
|
+
let watchdogStarted = false;
|
|
391
|
+
function registryDirectory() {
|
|
392
|
+
const base = process.env.XDG_RUNTIME_DIR || tmpdir();
|
|
393
|
+
return path.join(base, `opencode-zellij-${process.getuid?.() ?? "user"}`);
|
|
394
|
+
}
|
|
395
|
+
function watchdogRegistryPath() {
|
|
396
|
+
return path.join(registryDirectory(), `panes-${process.pid}-${instanceId}.json`);
|
|
397
|
+
}
|
|
398
|
+
function parseLinuxProcessStartTime(stat) {
|
|
399
|
+
return stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/)[19] ?? null;
|
|
400
|
+
}
|
|
401
|
+
function linuxProcessStartTime(pid) {
|
|
402
|
+
try {
|
|
403
|
+
return parseLinuxProcessStartTime(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
404
|
+
} catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function emptyRegistry() {
|
|
409
|
+
return {
|
|
410
|
+
version: 1,
|
|
411
|
+
instanceId,
|
|
412
|
+
ownerPid: process.pid,
|
|
413
|
+
ownerStartTime: linuxProcessStartTime(process.pid),
|
|
414
|
+
zellijSessionName: process.env.ZELLIJ_SESSION_NAME?.trim() || null,
|
|
415
|
+
panes: []
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function readRegistry() {
|
|
419
|
+
const file = watchdogRegistryPath();
|
|
420
|
+
if (!existsSync(file)) return emptyRegistry();
|
|
421
|
+
try {
|
|
422
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
423
|
+
if (parsed.version !== 1 || parsed.instanceId !== instanceId || parsed.ownerPid !== process.pid || !Array.isArray(parsed.panes)) return emptyRegistry();
|
|
424
|
+
return parsed;
|
|
425
|
+
} catch {
|
|
426
|
+
return emptyRegistry();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function writeRegistry(registry) {
|
|
430
|
+
mkdirSync(registryDirectory(), {
|
|
431
|
+
recursive: true,
|
|
432
|
+
mode: 448
|
|
433
|
+
});
|
|
434
|
+
const file = watchdogRegistryPath();
|
|
435
|
+
const tempFile = `${file}.tmp-${process.pid}`;
|
|
436
|
+
writeFileSync(tempFile, JSON.stringify(registry, null, 2), { mode: 384 });
|
|
437
|
+
renameSync(tempFile, file);
|
|
438
|
+
}
|
|
439
|
+
function ensureWatchdog() {
|
|
440
|
+
if (watchdogStarted) return;
|
|
441
|
+
watchdogStarted = true;
|
|
442
|
+
spawn("node", [watchdogRunnerPath(), watchdogRegistryPath()], {
|
|
443
|
+
detached: true,
|
|
444
|
+
stdio: "ignore",
|
|
445
|
+
env: process.env
|
|
446
|
+
}).unref();
|
|
447
|
+
}
|
|
448
|
+
function watchdogRunnerPath() {
|
|
449
|
+
return fileURLToPath(new URL("./pane-watchdog-runner.mjs", import.meta.url));
|
|
450
|
+
}
|
|
451
|
+
function cleanupStaleWatchdogRegistries() {
|
|
452
|
+
const directory = registryDirectory();
|
|
453
|
+
if (!existsSync(directory)) return;
|
|
454
|
+
for (const fileName of readdirSync(directory)) {
|
|
455
|
+
if (!fileName.startsWith("panes-") || !fileName.endsWith(".json")) continue;
|
|
456
|
+
const file = path.join(directory, fileName);
|
|
457
|
+
try {
|
|
458
|
+
const registry = JSON.parse(readFileSync(file, "utf8"));
|
|
459
|
+
if (registry.version !== 1 || ownerStillMatches(registry)) continue;
|
|
460
|
+
closeRegistryPanes(registry);
|
|
461
|
+
rmSync(file, { force: true });
|
|
462
|
+
} catch {
|
|
463
|
+
rmSync(file, { force: true });
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function ownerStillMatches(registry) {
|
|
468
|
+
try {
|
|
469
|
+
process.kill(registry.ownerPid, 0);
|
|
470
|
+
} catch {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return !registry.ownerStartTime || linuxProcessStartTime(registry.ownerPid) === registry.ownerStartTime;
|
|
474
|
+
}
|
|
475
|
+
function closeRegistryPanes(registry) {
|
|
476
|
+
for (const pane of registry.panes) {
|
|
477
|
+
const args = [];
|
|
478
|
+
if (registry.zellijSessionName) args.push("--session", registry.zellijSessionName);
|
|
479
|
+
args.push("action", "close-pane", "--pane-id", pane.paneId);
|
|
480
|
+
spawn("zellij", args, {
|
|
481
|
+
detached: true,
|
|
482
|
+
stdio: "ignore",
|
|
483
|
+
env: process.env
|
|
484
|
+
}).unref();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function upsertWatchdogPane(registry, session) {
|
|
488
|
+
return {
|
|
489
|
+
...registry,
|
|
490
|
+
panes: [...registry.panes.filter((pane) => pane.sessionId !== session.id && pane.paneId !== session.paneId), {
|
|
491
|
+
sessionId: session.id,
|
|
492
|
+
paneId: session.paneId,
|
|
493
|
+
title: session.title,
|
|
494
|
+
openCodeSessionId: session.openCodeSessionId,
|
|
495
|
+
createdAt: session.createdAt
|
|
496
|
+
}]
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function removeWatchdogPane(registry, sessionId) {
|
|
500
|
+
return {
|
|
501
|
+
...registry,
|
|
502
|
+
panes: registry.panes.filter((pane) => pane.sessionId !== sessionId)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function registerPaneForWatchdog(session) {
|
|
506
|
+
writeRegistry(upsertWatchdogPane(readRegistry(), session));
|
|
507
|
+
ensureWatchdog();
|
|
508
|
+
}
|
|
509
|
+
function unregisterPaneFromWatchdog(sessionId) {
|
|
510
|
+
const registry = readRegistry();
|
|
511
|
+
const updated = removeWatchdogPane(registry, sessionId);
|
|
512
|
+
if (updated.panes.length === registry.panes.length) return;
|
|
513
|
+
if (updated.panes.length === 0) {
|
|
514
|
+
removeWatchdogRegistry();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
writeRegistry(updated);
|
|
518
|
+
}
|
|
519
|
+
function removeWatchdogRegistry() {
|
|
520
|
+
try {
|
|
521
|
+
rmSync(watchdogRegistryPath(), { force: true });
|
|
522
|
+
} catch {}
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
246
525
|
//#region src/pty/ring-buffer.ts
|
|
247
526
|
const ansiPattern$1 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[a-z]`, "gi");
|
|
248
527
|
function normalizeLines(input) {
|
|
@@ -501,6 +780,7 @@ var SubscriberManager = class {
|
|
|
501
780
|
state.buffer.append(`[zellij-pty] Pane ${session.paneId} closed at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
502
781
|
this.sessions.updateLineCount(sessionId, state.buffer.lineCount);
|
|
503
782
|
this.sessions.updateStatus(sessionId, session.status === "killed" ? "killed" : "exited");
|
|
783
|
+
unregisterPaneFromWatchdog(sessionId);
|
|
504
784
|
this.stop(sessionId);
|
|
505
785
|
return;
|
|
506
786
|
}
|
|
@@ -629,7 +909,7 @@ const zellijPtyKillTool = tool({
|
|
|
629
909
|
const output = subscriberManager.has(session.id) ? readOutputSnapshot(session.id) : void 0;
|
|
630
910
|
try {
|
|
631
911
|
await zellijCli.sendCtrlC(session.paneId);
|
|
632
|
-
await setTimeout(500);
|
|
912
|
+
await setTimeout$1(500);
|
|
633
913
|
} catch (error) {
|
|
634
914
|
warnings.push(`Ctrl-C failed or pane was already gone: ${error instanceof Error ? error.message : String(error)}`);
|
|
635
915
|
}
|
|
@@ -649,6 +929,7 @@ const zellijPtyKillTool = tool({
|
|
|
649
929
|
}
|
|
650
930
|
subscriberManager.stop(session.id);
|
|
651
931
|
subscriberManager.forget(session.id);
|
|
932
|
+
unregisterPaneFromWatchdog(session.id);
|
|
652
933
|
sessionManager.remove(session.id);
|
|
653
934
|
return jsonResponse({
|
|
654
935
|
killed: true,
|
|
@@ -784,7 +1065,7 @@ const requestSudoTool = tool({
|
|
|
784
1065
|
humanInputOnly: true
|
|
785
1066
|
});
|
|
786
1067
|
const command = buildReviewScript(args.summary, args.scripts);
|
|
787
|
-
const title = createOpenCodePaneTitle("
|
|
1068
|
+
const title = createOpenCodePaneTitle("zellij_pty_request_sudo");
|
|
788
1069
|
const paneId = await zellijCli.newPane({
|
|
789
1070
|
command: "bash",
|
|
790
1071
|
args: ["-lc", command],
|
|
@@ -804,13 +1085,14 @@ const requestSudoTool = tool({
|
|
|
804
1085
|
openCodeSessionId: context.sessionID,
|
|
805
1086
|
paneId,
|
|
806
1087
|
title,
|
|
807
|
-
command: "
|
|
1088
|
+
command: "zellij_pty_request_sudo",
|
|
808
1089
|
args: [],
|
|
809
1090
|
cwd,
|
|
810
1091
|
allowAgentInput: false,
|
|
811
1092
|
humanInputOnly: true,
|
|
812
1093
|
exitCodeToken
|
|
813
1094
|
});
|
|
1095
|
+
registerPaneForWatchdog(session);
|
|
814
1096
|
await subscriberManager.start(session);
|
|
815
1097
|
return jsonResponse({
|
|
816
1098
|
session: publicSession(session),
|
|
@@ -833,7 +1115,7 @@ async function runProbe(probe, outputReader) {
|
|
|
833
1115
|
};
|
|
834
1116
|
if (effectiveProbe.type === "sleep") {
|
|
835
1117
|
const seconds = effectiveProbe.seconds ?? defaultSleepSeconds;
|
|
836
|
-
await setTimeout(seconds * 1e3);
|
|
1118
|
+
await setTimeout$1(seconds * 1e3);
|
|
837
1119
|
return result(effectiveProbe.type, true, `Slept for ${seconds}s.`, startedAt);
|
|
838
1120
|
}
|
|
839
1121
|
if (effectiveProbe.type === "output") {
|
|
@@ -841,7 +1123,7 @@ async function runProbe(probe, outputReader) {
|
|
|
841
1123
|
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
842
1124
|
while (Date.now() <= deadline) {
|
|
843
1125
|
if (outputReader(effectiveProbe.grep, effectiveProbe.ignoreCase)) return result(effectiveProbe.type, true, `Observed output matching /${effectiveProbe.grep}/.`, startedAt);
|
|
844
|
-
await setTimeout(pollIntervalMs);
|
|
1126
|
+
await setTimeout$1(pollIntervalMs);
|
|
845
1127
|
}
|
|
846
1128
|
return result(effectiveProbe.type, false, `Timed out after ${timeoutSeconds}s waiting for output matching /${effectiveProbe.grep}/.`, startedAt);
|
|
847
1129
|
}
|
|
@@ -861,7 +1143,7 @@ async function runProbe(probe, outputReader) {
|
|
|
861
1143
|
} catch (error) {
|
|
862
1144
|
lastError = error instanceof Error ? error.message : String(error);
|
|
863
1145
|
}
|
|
864
|
-
await setTimeout(pollIntervalMs);
|
|
1146
|
+
await setTimeout$1(pollIntervalMs);
|
|
865
1147
|
}
|
|
866
1148
|
return result(effectiveProbe.type, false, `Timed out after ${timeoutSeconds}s probing ${effectiveProbe.url}: ${lastError}.`, startedAt);
|
|
867
1149
|
}
|
|
@@ -934,6 +1216,7 @@ const zellijPtySpawnTool = tool({
|
|
|
934
1216
|
humanInputOnly: false,
|
|
935
1217
|
exitCodeToken
|
|
936
1218
|
});
|
|
1219
|
+
registerPaneForWatchdog(session);
|
|
937
1220
|
await subscriberManager.start(session);
|
|
938
1221
|
const probe = await runProbe(args.probe, (grep, ignoreCase) => outputMatches(session.id, grep, ignoreCase));
|
|
939
1222
|
const output = readOutputSnapshot(session.id, { maxLines: args.maxLines });
|
|
@@ -982,7 +1265,7 @@ const schema = tool.schema;
|
|
|
982
1265
|
const zellijPtyWriteTool = tool({
|
|
983
1266
|
description: "Write stdin to a Zellij PTY session. Refuses human-input-only sessions.",
|
|
984
1267
|
args: {
|
|
985
|
-
id: schema.string().describe("zellij-pty session id returned by zellij_pty_spawn or
|
|
1268
|
+
id: schema.string().describe("zellij-pty session id returned by zellij_pty_spawn or zellij_pty_request_sudo."),
|
|
986
1269
|
data: schema.string().describe("Text to write. Use to send Ctrl-C."),
|
|
987
1270
|
maxLines: schema.number().int().positive().max(5e3).optional().describe("Maximum recent output lines to return. Defaults to 200."),
|
|
988
1271
|
interruptAfterSeconds: schema.number().positive().max(300).optional().describe("Blindly send Ctrl-C after this many seconds if the pane is still running; keeps the pane alive.")
|
|
@@ -1002,12 +1285,12 @@ const zellijPtyWriteTool = tool({
|
|
|
1002
1285
|
}
|
|
1003
1286
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
1287
|
if (args.interruptAfterSeconds) {
|
|
1005
|
-
await setTimeout(args.interruptAfterSeconds * 1e3);
|
|
1288
|
+
await setTimeout$1(args.interruptAfterSeconds * 1e3);
|
|
1006
1289
|
if (sessionManager.get(session.id).status === "running") {
|
|
1007
1290
|
await zellijCli.sendCtrlC(session.paneId);
|
|
1008
|
-
await setTimeout(500);
|
|
1291
|
+
await setTimeout$1(500);
|
|
1009
1292
|
}
|
|
1010
|
-
} else await setTimeout(1e3);
|
|
1293
|
+
} else await setTimeout$1(1e3);
|
|
1011
1294
|
return jsonResponse({
|
|
1012
1295
|
session: publicSession(session),
|
|
1013
1296
|
output: readOutputSnapshot(session.id, { maxLines: args.maxLines }),
|
|
@@ -1017,28 +1300,336 @@ const zellijPtyWriteTool = tool({
|
|
|
1017
1300
|
}
|
|
1018
1301
|
});
|
|
1019
1302
|
//#endregion
|
|
1303
|
+
//#region src/utils/debug.ts
|
|
1304
|
+
function debug(message, ...details) {
|
|
1305
|
+
if (!process.env.ZELLIJ_PTY_DEBUG) return;
|
|
1306
|
+
console.warn(`[opencode-zellij] ${message}`, ...details);
|
|
1307
|
+
}
|
|
1308
|
+
//#endregion
|
|
1309
|
+
//#region src/zellij/shutdown-cleanup.ts
|
|
1310
|
+
let registered = false;
|
|
1311
|
+
let cleanedUp = false;
|
|
1312
|
+
function cleanupPanesOnShutdown(sessions = sessionManager, subscribers = subscriberManager) {
|
|
1313
|
+
if (cleanedUp) return;
|
|
1314
|
+
cleanedUp = true;
|
|
1315
|
+
for (const session of sessions.list()) {
|
|
1316
|
+
try {
|
|
1317
|
+
zellijCli.closePaneSync(session.paneId);
|
|
1318
|
+
} catch {}
|
|
1319
|
+
subscribers.forget(session.id);
|
|
1320
|
+
try {
|
|
1321
|
+
sessions.remove(session.id);
|
|
1322
|
+
} catch {}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function registerShutdownCleanup() {
|
|
1326
|
+
if (registered) return;
|
|
1327
|
+
registered = true;
|
|
1328
|
+
process.once("exit", () => cleanupPanesOnShutdown());
|
|
1329
|
+
process.once("SIGINT", () => exitAfterCleanup("SIGINT", 130));
|
|
1330
|
+
process.once("SIGTERM", () => exitAfterCleanup("SIGTERM", 143));
|
|
1331
|
+
process.once("SIGHUP", () => exitAfterCleanup("SIGHUP", 129));
|
|
1332
|
+
}
|
|
1333
|
+
function exitAfterCleanup(signal, code) {
|
|
1334
|
+
cleanupPanesOnShutdown();
|
|
1335
|
+
process.removeAllListeners(signal);
|
|
1336
|
+
process.exit(code);
|
|
1337
|
+
}
|
|
1338
|
+
//#endregion
|
|
1339
|
+
//#region src/zellij/tab-title-events.ts
|
|
1340
|
+
const execFileAsync = promisify(execFile);
|
|
1341
|
+
function isRecord(value) {
|
|
1342
|
+
return typeof value === "object" && value !== null;
|
|
1343
|
+
}
|
|
1344
|
+
function stringProperty(object, key) {
|
|
1345
|
+
const value = object[key];
|
|
1346
|
+
return typeof value === "string" ? value : void 0;
|
|
1347
|
+
}
|
|
1348
|
+
function nestedStringProperty(object, key, nestedKey) {
|
|
1349
|
+
const nested = object[key];
|
|
1350
|
+
if (!isRecord(nested)) return void 0;
|
|
1351
|
+
return stringProperty(nested, nestedKey);
|
|
1352
|
+
}
|
|
1353
|
+
function sessionStatusProperty(object) {
|
|
1354
|
+
const status = object.status;
|
|
1355
|
+
if (!isRecord(status)) return void 0;
|
|
1356
|
+
if (status.type === "idle" || status.type === "busy") return { type: status.type };
|
|
1357
|
+
if (status.type === "retry") return {
|
|
1358
|
+
type: "retry",
|
|
1359
|
+
attempt: typeof status.attempt === "number" ? status.attempt : 0,
|
|
1360
|
+
message: typeof status.message === "string" ? status.message : "",
|
|
1361
|
+
next: typeof status.next === "number" ? status.next : 0
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
function inputRequestID(object) {
|
|
1365
|
+
return stringProperty(object, "id") ?? stringProperty(object, "requestID") ?? stringProperty(object, "permissionID");
|
|
1366
|
+
}
|
|
1367
|
+
function deletedSessionID(event) {
|
|
1368
|
+
if (!isRecord(event.properties)) return void 0;
|
|
1369
|
+
return nestedStringProperty(event.properties, "info", "id") ?? stringProperty(event.properties, "sessionID");
|
|
1370
|
+
}
|
|
1371
|
+
async function readGitBranch(worktree) {
|
|
1372
|
+
return (await execFileAsync("git", [
|
|
1373
|
+
"-C",
|
|
1374
|
+
worktree,
|
|
1375
|
+
"branch",
|
|
1376
|
+
"--show-current"
|
|
1377
|
+
], {
|
|
1378
|
+
encoding: "utf8",
|
|
1379
|
+
timeout: 1e3,
|
|
1380
|
+
maxBuffer: 1024 * 1024
|
|
1381
|
+
})).stdout;
|
|
1382
|
+
}
|
|
1383
|
+
async function getInitialBranch(worktree, readBranch = readGitBranch) {
|
|
1384
|
+
try {
|
|
1385
|
+
return (await readBranch(worktree)).trim() || void 0;
|
|
1386
|
+
} catch {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function shouldReadInitialBranch(zellij) {
|
|
1391
|
+
return Boolean(zellij);
|
|
1392
|
+
}
|
|
1393
|
+
function handleTabTitleEvent(tabTitleManager, event) {
|
|
1394
|
+
if (!isRecord(event.properties)) return;
|
|
1395
|
+
const properties = event.properties;
|
|
1396
|
+
switch (event.type) {
|
|
1397
|
+
case "session.status": {
|
|
1398
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
1399
|
+
const status = sessionStatusProperty(properties);
|
|
1400
|
+
if (sessionID && status) tabTitleManager.updateSessionStatus(sessionID, status);
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
case "session.idle": {
|
|
1404
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
1405
|
+
if (sessionID) tabTitleManager.markSessionIdle(sessionID);
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
case "vcs.branch.updated":
|
|
1409
|
+
tabTitleManager.setBranch(stringProperty(properties, "branch"));
|
|
1410
|
+
break;
|
|
1411
|
+
case "question.asked":
|
|
1412
|
+
case "permission.asked":
|
|
1413
|
+
case "permission.updated": {
|
|
1414
|
+
const id = inputRequestID(properties);
|
|
1415
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
1416
|
+
if (id && sessionID) tabTitleManager.markNeedsInput(id, sessionID);
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
case "question.replied":
|
|
1420
|
+
case "question.rejected":
|
|
1421
|
+
case "permission.replied": {
|
|
1422
|
+
const id = inputRequestID(properties);
|
|
1423
|
+
if (id) tabTitleManager.clearNeedsInput(id);
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
case "session.deleted": {
|
|
1427
|
+
const sessionID = deletedSessionID(event);
|
|
1428
|
+
if (sessionID) tabTitleManager.removeSession(sessionID);
|
|
1429
|
+
break;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
//#endregion
|
|
1434
|
+
//#region src/zellij/tab-title.ts
|
|
1435
|
+
const defaultTabTitleEmojis = {
|
|
1436
|
+
idle: "🟢",
|
|
1437
|
+
running: "⚡",
|
|
1438
|
+
needsInput: "💬",
|
|
1439
|
+
branch: "🌱"
|
|
1440
|
+
};
|
|
1441
|
+
function formatTabTitle(context) {
|
|
1442
|
+
const branch = context.branchName ? ` ${context.emojis.branch} ${context.branchName}` : "";
|
|
1443
|
+
return `${context.emojis[context.status === "needs-input" ? "needsInput" : context.status]} ${context.projectName}${branch}`;
|
|
1444
|
+
}
|
|
1445
|
+
function sanitizeTitle(title, maxLength = 90) {
|
|
1446
|
+
let cleaned = title.replace(/[\p{Cc}\p{Cf}\p{Co}\p{Cn}]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1447
|
+
const chars = Array.from(cleaned);
|
|
1448
|
+
if (chars.length > maxLength) cleaned = `${chars.slice(0, maxLength - 1).join("")}…`;
|
|
1449
|
+
return cleaned;
|
|
1450
|
+
}
|
|
1451
|
+
var TabTitleManager = class {
|
|
1452
|
+
sessionStatuses = /* @__PURE__ */ new Map();
|
|
1453
|
+
pendingInputs = /* @__PURE__ */ new Map();
|
|
1454
|
+
branchName;
|
|
1455
|
+
desiredTitle;
|
|
1456
|
+
lastSyncedTitle;
|
|
1457
|
+
debounceTimer;
|
|
1458
|
+
syncInFlight = false;
|
|
1459
|
+
debounceMs;
|
|
1460
|
+
projectName;
|
|
1461
|
+
cli;
|
|
1462
|
+
emojis;
|
|
1463
|
+
enabled;
|
|
1464
|
+
constructor(options) {
|
|
1465
|
+
this.projectName = options.projectName;
|
|
1466
|
+
this.branchName = options.branchName?.trim() || void 0;
|
|
1467
|
+
this.cli = options.cli ?? new ZellijCli();
|
|
1468
|
+
this.emojis = {
|
|
1469
|
+
...defaultTabTitleEmojis,
|
|
1470
|
+
...options.emojis
|
|
1471
|
+
};
|
|
1472
|
+
this.debounceMs = options.debounceMs ?? 300;
|
|
1473
|
+
this.enabled = Boolean(process.env.ZELLIJ);
|
|
1474
|
+
}
|
|
1475
|
+
setBranch(branch) {
|
|
1476
|
+
const trimmed = branch?.trim() || void 0;
|
|
1477
|
+
if (this.branchName === trimmed) return;
|
|
1478
|
+
this.branchName = trimmed;
|
|
1479
|
+
this.scheduleUpdate();
|
|
1480
|
+
}
|
|
1481
|
+
updateSessionStatus(sessionID, status) {
|
|
1482
|
+
const activity = status.type === "idle" ? "idle" : "running";
|
|
1483
|
+
if (this.sessionStatuses.get(sessionID) === activity) return;
|
|
1484
|
+
this.sessionStatuses.set(sessionID, activity);
|
|
1485
|
+
this.scheduleUpdate();
|
|
1486
|
+
}
|
|
1487
|
+
markSessionIdle(sessionID) {
|
|
1488
|
+
this.updateSessionStatus(sessionID, { type: "idle" });
|
|
1489
|
+
}
|
|
1490
|
+
removeSession(sessionID) {
|
|
1491
|
+
const hadSessionStatus = this.sessionStatuses.delete(sessionID);
|
|
1492
|
+
let hadPendingInput = false;
|
|
1493
|
+
for (const [id, pendingSessionID] of this.pendingInputs) if (pendingSessionID === sessionID) {
|
|
1494
|
+
this.pendingInputs.delete(id);
|
|
1495
|
+
hadPendingInput = true;
|
|
1496
|
+
}
|
|
1497
|
+
if (!hadSessionStatus && !hadPendingInput) return;
|
|
1498
|
+
this.scheduleUpdate();
|
|
1499
|
+
}
|
|
1500
|
+
markNeedsInput(id, sessionID) {
|
|
1501
|
+
if (this.pendingInputs.get(id) === sessionID) return;
|
|
1502
|
+
this.pendingInputs.set(id, sessionID);
|
|
1503
|
+
this.scheduleUpdate();
|
|
1504
|
+
}
|
|
1505
|
+
clearNeedsInput(id) {
|
|
1506
|
+
if (!this.pendingInputs.delete(id)) return;
|
|
1507
|
+
this.scheduleUpdate();
|
|
1508
|
+
}
|
|
1509
|
+
get isBusy() {
|
|
1510
|
+
for (const activity of this.sessionStatuses.values()) if (activity === "running") return true;
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
get needsInput() {
|
|
1514
|
+
return this.pendingInputs.size > 0;
|
|
1515
|
+
}
|
|
1516
|
+
get status() {
|
|
1517
|
+
if (this.needsInput) return "needs-input";
|
|
1518
|
+
if (this.isBusy) return "running";
|
|
1519
|
+
return "idle";
|
|
1520
|
+
}
|
|
1521
|
+
buildTitle() {
|
|
1522
|
+
return sanitizeTitle(formatTabTitle({
|
|
1523
|
+
projectName: this.projectName,
|
|
1524
|
+
branchName: this.branchName,
|
|
1525
|
+
status: this.status,
|
|
1526
|
+
emojis: this.emojis
|
|
1527
|
+
}));
|
|
1528
|
+
}
|
|
1529
|
+
getCurrentTitle() {
|
|
1530
|
+
return this.buildTitle();
|
|
1531
|
+
}
|
|
1532
|
+
async renderImmediate() {
|
|
1533
|
+
if (!this.enabled) return;
|
|
1534
|
+
this.desiredTitle = this.buildTitle();
|
|
1535
|
+
this.clearDebounceTimer();
|
|
1536
|
+
await this.syncDesiredTitle();
|
|
1537
|
+
}
|
|
1538
|
+
scheduleUpdate() {
|
|
1539
|
+
if (!this.enabled) return;
|
|
1540
|
+
const title = this.buildTitle();
|
|
1541
|
+
if (title === this.desiredTitle && title === this.lastSyncedTitle) return;
|
|
1542
|
+
this.desiredTitle = title;
|
|
1543
|
+
if (this.syncInFlight) return;
|
|
1544
|
+
this.clearDebounceTimer();
|
|
1545
|
+
this.debounceTimer = setTimeout(() => {
|
|
1546
|
+
this.debounceTimer = void 0;
|
|
1547
|
+
this.syncDesiredTitle().catch(() => {});
|
|
1548
|
+
}, this.debounceMs);
|
|
1549
|
+
}
|
|
1550
|
+
async syncDesiredTitle() {
|
|
1551
|
+
if (!this.enabled) return;
|
|
1552
|
+
if (this.syncInFlight) return;
|
|
1553
|
+
this.syncInFlight = true;
|
|
1554
|
+
try {
|
|
1555
|
+
while (this.desiredTitle && this.desiredTitle !== this.lastSyncedTitle) {
|
|
1556
|
+
const title = this.desiredTitle;
|
|
1557
|
+
try {
|
|
1558
|
+
await this.cli.renameTab(title);
|
|
1559
|
+
this.lastSyncedTitle = title;
|
|
1560
|
+
} catch (cause) {
|
|
1561
|
+
debug("Failed to rename Zellij tab.", cause);
|
|
1562
|
+
break;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
} finally {
|
|
1566
|
+
this.syncInFlight = false;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
clearDebounceTimer() {
|
|
1570
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1571
|
+
this.debounceTimer = void 0;
|
|
1572
|
+
}
|
|
1573
|
+
destroy() {
|
|
1574
|
+
this.clearDebounceTimer();
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
//#endregion
|
|
1020
1578
|
//#region src/plugin.ts
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1579
|
+
const ptyTools = {
|
|
1580
|
+
zellij_pty_spawn: zellijPtySpawnTool,
|
|
1581
|
+
zellij_pty_list: zellijPtyListTool,
|
|
1582
|
+
zellij_pty_write: zellijPtyWriteTool,
|
|
1583
|
+
zellij_pty_read: zellijPtyReadTool,
|
|
1584
|
+
zellij_pty_kill: zellijPtyKillTool
|
|
1585
|
+
};
|
|
1586
|
+
function getProjectName(path) {
|
|
1587
|
+
return path.split(/[/\\]/).filter(Boolean).pop() || "opencode";
|
|
1588
|
+
}
|
|
1589
|
+
function getWorkspaceRoot(input) {
|
|
1590
|
+
return input.worktree || input.directory || process.cwd();
|
|
1591
|
+
}
|
|
1592
|
+
const ZellijPtyPlugin = async (input) => {
|
|
1593
|
+
const { config, warnings } = await loadConfig(input);
|
|
1594
|
+
for (const warning of warnings) debug(warning);
|
|
1595
|
+
configurePolicy({ allowSudoPane: config.pty.sudoPane === "allow" });
|
|
1596
|
+
cleanupStaleWatchdogRegistries();
|
|
1597
|
+
registerShutdownCleanup();
|
|
1598
|
+
const workspaceRoot = getWorkspaceRoot(input);
|
|
1599
|
+
const projectName = getProjectName(workspaceRoot);
|
|
1600
|
+
const branchName = config.tabTitle.enabled && shouldReadInitialBranch(process.env.ZELLIJ) ? await getInitialBranch(workspaceRoot) : void 0;
|
|
1601
|
+
const tabTitleManager = config.tabTitle.enabled ? new TabTitleManager({
|
|
1602
|
+
projectName,
|
|
1603
|
+
branchName,
|
|
1604
|
+
debounceMs: config.tabTitle.debounceMs,
|
|
1605
|
+
emojis: {
|
|
1606
|
+
idle: config.tabTitle.emojiIdle,
|
|
1607
|
+
running: config.tabTitle.emojiRunning,
|
|
1608
|
+
needsInput: config.tabTitle.emojiNeedsInput,
|
|
1609
|
+
branch: config.tabTitle.emojiBranch
|
|
1610
|
+
}
|
|
1611
|
+
}) : void 0;
|
|
1612
|
+
tabTitleManager?.renderImmediate().catch(() => {});
|
|
1023
1613
|
return {
|
|
1024
1614
|
async event(input) {
|
|
1025
|
-
|
|
1026
|
-
|
|
1615
|
+
const event = input.event;
|
|
1616
|
+
if (tabTitleManager) handleTabTitleEvent(tabTitleManager, event);
|
|
1617
|
+
if (event.type === "session.deleted") {
|
|
1618
|
+
const sessionID = deletedSessionID(event);
|
|
1619
|
+
if (!sessionID) return;
|
|
1620
|
+
const sessions = sessionManager.listByOpenCodeSession(sessionID);
|
|
1027
1621
|
await Promise.all(sessions.map(async (session) => {
|
|
1028
1622
|
await subscriberManager.closeSessionPane(session.id);
|
|
1029
1623
|
subscriberManager.forget(session.id);
|
|
1624
|
+
unregisterPaneFromWatchdog(session.id);
|
|
1030
1625
|
sessionManager.remove(session.id);
|
|
1031
1626
|
}));
|
|
1032
1627
|
}
|
|
1033
1628
|
},
|
|
1034
|
-
tool: {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
zellij_pty_read: zellijPtyReadTool,
|
|
1039
|
-
zellij_pty_kill: zellijPtyKillTool,
|
|
1040
|
-
request_sudo: requestSudoTool
|
|
1041
|
-
}
|
|
1629
|
+
tool: config.pty.enabled ? {
|
|
1630
|
+
...ptyTools,
|
|
1631
|
+
...config.pty.sudoPane === "hide" ? {} : { zellij_pty_request_sudo: requestSudoTool }
|
|
1632
|
+
} : {}
|
|
1042
1633
|
};
|
|
1043
1634
|
};
|
|
1044
1635
|
//#endregion
|