opencode-zellij 0.0.16 → 0.0.17
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 +13 -2
- package/README.zh.md +13 -2
- package/dist/index.d.mts +9 -107
- package/dist/index.mjs +163 -642
- package/dist/index.mjs.map +1 -1
- package/dist/pane-watchdog-runner.mjs +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -1,262 +1,26 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
3
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { readFile
|
|
5
|
-
import path, { basename, dirname, join } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { promisify } from "node:util";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
8
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import path, { dirname, join } from "node:path";
|
|
9
6
|
import { parseJSON, parseJSONC } from "confbox";
|
|
10
7
|
import { z } from "zod";
|
|
11
8
|
import { randomUUID } from "node:crypto";
|
|
12
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
13
10
|
import { tool } from "@opencode-ai/plugin";
|
|
11
|
+
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { Buffer } from "node:buffer";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.warn(`[opencode-zellij] ${message}`, ...details);
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/utils/errors.ts
|
|
22
|
-
function errorMessage(error) {
|
|
23
|
-
return error instanceof Error ? error.message : String(error);
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/auto-update.ts
|
|
27
|
-
const PACKAGE_NAME = "opencode-zellij";
|
|
28
|
-
const NPM_REGISTRY_URL = "https://registry.npmjs.org/-/package/opencode-zellij/dist-tags";
|
|
29
|
-
const FETCH_TIMEOUT_MS = 5e3;
|
|
30
|
-
const INSTALL_TIMEOUT_MS = 6e4;
|
|
31
|
-
const defaultExecFile = promisify(execFile);
|
|
32
|
-
function packageDir(installRoot) {
|
|
33
|
-
return join(installRoot, "node_modules", PACKAGE_NAME);
|
|
34
|
-
}
|
|
35
|
-
function backupDir(installRoot) {
|
|
36
|
-
return join(installRoot, "node_modules", `${PACKAGE_NAME}.update-backup`);
|
|
37
|
-
}
|
|
38
|
-
async function installedPackageMetadata(installRoot) {
|
|
39
|
-
try {
|
|
40
|
-
const content = await readFile(join(packageDir(installRoot), "package.json"), "utf8");
|
|
41
|
-
const pkg = JSON.parse(content);
|
|
42
|
-
if (isRecord$2(pkg)) return {
|
|
43
|
-
name: typeof pkg.name === "string" ? pkg.name : void 0,
|
|
44
|
-
version: typeof pkg.version === "string" ? pkg.version : void 0,
|
|
45
|
-
main: typeof pkg.main === "string" ? pkg.main : void 0
|
|
46
|
-
};
|
|
47
|
-
} catch (error) {
|
|
48
|
-
debug("installedPackageMetadata failed", errorMessage(error));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function isExpectedPackage(metadata, version) {
|
|
52
|
-
return metadata?.name === "opencode-zellij" && metadata.version === version;
|
|
53
|
-
}
|
|
54
|
-
function hasRunnableEntry(installRoot, metadata) {
|
|
55
|
-
if (!metadata) return false;
|
|
56
|
-
const dir = packageDir(installRoot);
|
|
57
|
-
if (metadata.main && existsSync(join(dir, metadata.main))) return true;
|
|
58
|
-
return existsSync(join(dir, "dist", "index.mjs"));
|
|
59
|
-
}
|
|
60
|
-
async function isVerifiedInstall(installRoot, version) {
|
|
61
|
-
const metadata = await installedPackageMetadata(installRoot);
|
|
62
|
-
return isExpectedPackage(metadata, version) && hasRunnableEntry(installRoot, metadata);
|
|
63
|
-
}
|
|
64
|
-
async function removeInstalledPackage(installRoot) {
|
|
65
|
-
await rm(packageDir(installRoot), {
|
|
66
|
-
force: true,
|
|
67
|
-
recursive: true
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
async function backupInstalledPackage(installRoot) {
|
|
71
|
-
const source = packageDir(installRoot);
|
|
72
|
-
if (!existsSync(source)) return void 0;
|
|
73
|
-
const backup = backupDir(installRoot);
|
|
74
|
-
await rm(backup, {
|
|
75
|
-
force: true,
|
|
76
|
-
recursive: true
|
|
77
|
-
});
|
|
78
|
-
await rename(source, backup);
|
|
79
|
-
return backup;
|
|
80
|
-
}
|
|
81
|
-
async function restoreInstalledPackage(installRoot, backup) {
|
|
82
|
-
if (!backup || !existsSync(backup)) return;
|
|
83
|
-
await rm(packageDir(installRoot), {
|
|
84
|
-
force: true,
|
|
85
|
-
recursive: true
|
|
86
|
-
});
|
|
87
|
-
await rename(backup, packageDir(installRoot));
|
|
88
|
-
}
|
|
89
|
-
async function discardBackup(backup) {
|
|
90
|
-
if (backup) await rm(backup, {
|
|
91
|
-
force: true,
|
|
92
|
-
recursive: true
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
async function findInstallContext(importMetaUrl) {
|
|
96
|
-
let startPath;
|
|
97
|
-
try {
|
|
98
|
-
startPath = fileURLToPath(importMetaUrl);
|
|
99
|
-
} catch (cause) {
|
|
100
|
-
debug("invalid import.meta.url", cause instanceof Error ? cause.message : String(cause));
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
let dir = dirname(startPath);
|
|
104
|
-
while (true) {
|
|
105
|
-
if (dir.endsWith(`/node_modules/opencode-zellij`) || dir.endsWith(`\\node_modules\\opencode-zellij`)) {
|
|
106
|
-
const packageJsonPath = join(dir, "package.json");
|
|
107
|
-
try {
|
|
108
|
-
const content = await readFile(packageJsonPath, "utf8");
|
|
109
|
-
const pkg = JSON.parse(content);
|
|
110
|
-
if (isRecord$2(pkg) && pkg.name === "opencode-zellij" && typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
111
|
-
const installRoot = dirname(dirname(dir));
|
|
112
|
-
if (existsSync(join(installRoot, "package.json"))) return {
|
|
113
|
-
installRoot,
|
|
114
|
-
cacheSpec: basename(installRoot),
|
|
115
|
-
currentVersion: pkg.version
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
debug("findInstallContext package.json read failed", errorMessage(error));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const parent = dirname(dir);
|
|
123
|
-
if (parent === dir) break;
|
|
124
|
-
dir = parent;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function isRecord$2(value) {
|
|
128
|
-
return typeof value === "object" && value !== null;
|
|
129
|
-
}
|
|
130
|
-
function isAutoUpdatableSpec(spec) {
|
|
131
|
-
if (spec === "opencode-zellij") return true;
|
|
132
|
-
if (spec === `opencode-zellij@latest`) return true;
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
async function fetchLatestVersion(fetchImpl = globalThis.fetch) {
|
|
136
|
-
const controller = new AbortController();
|
|
137
|
-
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
138
|
-
try {
|
|
139
|
-
const response = await fetchImpl(NPM_REGISTRY_URL, { signal: controller.signal });
|
|
140
|
-
clearTimeout(timeout);
|
|
141
|
-
if (!response.ok) {
|
|
142
|
-
debug(`npm registry returned ${response.status}`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const data = await response.json();
|
|
146
|
-
if (isRecord$2(data) && typeof data.latest === "string") return data.latest;
|
|
147
|
-
debug("npm registry response missing latest tag");
|
|
148
|
-
return;
|
|
149
|
-
} catch (cause) {
|
|
150
|
-
clearTimeout(timeout);
|
|
151
|
-
debug("failed to fetch latest version", cause instanceof Error ? cause.message : String(cause));
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
async function runNpmInstall(installRoot, version, execImpl = defaultExecFile) {
|
|
156
|
-
debug(`updating ${PACKAGE_NAME} to ${version} in ${installRoot}`);
|
|
157
|
-
try {
|
|
158
|
-
const install = () => execImpl("npm", [
|
|
159
|
-
"install",
|
|
160
|
-
`${PACKAGE_NAME}@${version}`,
|
|
161
|
-
"--save-exact",
|
|
162
|
-
"--ignore-scripts",
|
|
163
|
-
"--no-audit",
|
|
164
|
-
"--no-fund",
|
|
165
|
-
"--prefer-online"
|
|
166
|
-
], {
|
|
167
|
-
cwd: installRoot,
|
|
168
|
-
timeout: INSTALL_TIMEOUT_MS
|
|
169
|
-
});
|
|
170
|
-
await install();
|
|
171
|
-
if (await isVerifiedInstall(installRoot, version)) {
|
|
172
|
-
debug(`updated ${PACKAGE_NAME} to ${version}`);
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
const installedPackage = await installedPackageMetadata(installRoot);
|
|
176
|
-
debug(`npm install left stale or invalid ${PACKAGE_NAME} (${installedPackage?.name ?? "<missing>"}@${installedPackage?.version ?? "<missing>"}); reinstalling ${version}`);
|
|
177
|
-
const backup = await backupInstalledPackage(installRoot);
|
|
178
|
-
try {
|
|
179
|
-
await removeInstalledPackage(installRoot);
|
|
180
|
-
await install();
|
|
181
|
-
if (await isVerifiedInstall(installRoot, version)) {
|
|
182
|
-
await discardBackup(backup);
|
|
183
|
-
debug(`updated ${PACKAGE_NAME} to ${version}`);
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
const reinstalledPackage = await installedPackageMetadata(installRoot);
|
|
187
|
-
debug(`npm install verification failed: expected ${PACKAGE_NAME}@${version}, found ${reinstalledPackage?.name ?? "<missing>"}@${reinstalledPackage?.version ?? "<missing>"}`);
|
|
188
|
-
await restoreInstalledPackage(installRoot, backup);
|
|
189
|
-
return false;
|
|
190
|
-
} catch (cause) {
|
|
191
|
-
await restoreInstalledPackage(installRoot, backup);
|
|
192
|
-
throw cause;
|
|
193
|
-
}
|
|
194
|
-
} catch (cause) {
|
|
195
|
-
debug("npm install failed", cause instanceof Error ? cause.message : String(cause));
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
async function checkAndUpdate(options) {
|
|
200
|
-
const context = await findInstallContext(options.importMetaUrl);
|
|
201
|
-
if (!context) {
|
|
202
|
-
debug("skipping auto-update: not installed from npm");
|
|
203
|
-
return {
|
|
204
|
-
type: "skipped",
|
|
205
|
-
reason: "not installed from npm"
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
if (!isAutoUpdatableSpec(context.cacheSpec)) {
|
|
209
|
-
debug(`skipping auto-update: cache spec is pinned or unknown (${context.cacheSpec})`);
|
|
210
|
-
return {
|
|
211
|
-
type: "skipped",
|
|
212
|
-
reason: `cache spec is pinned or unknown (${context.cacheSpec})`
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
const latest = await fetchLatestVersion(options.fetchImpl);
|
|
216
|
-
if (!latest) {
|
|
217
|
-
debug("skipping auto-update: could not determine latest version");
|
|
218
|
-
return {
|
|
219
|
-
type: "skipped",
|
|
220
|
-
reason: "could not determine latest version"
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
const installedVersion = (await installedPackageMetadata(context.installRoot))?.version ?? context.currentVersion;
|
|
224
|
-
if (latest === installedVersion) {
|
|
225
|
-
debug(`auto-update: already on latest ${latest}`);
|
|
226
|
-
return {
|
|
227
|
-
type: "up-to-date",
|
|
228
|
-
currentVersion: installedVersion
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
if (await runNpmInstall(context.installRoot, latest, options.execImpl)) {
|
|
232
|
-
debug(`updated ${PACKAGE_NAME} from ${installedVersion} to ${latest}`);
|
|
233
|
-
return {
|
|
234
|
-
type: "updated",
|
|
235
|
-
fromVersion: installedVersion,
|
|
236
|
-
toVersion: latest
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
type: "failed",
|
|
241
|
-
currentVersion: installedVersion,
|
|
242
|
-
latestVersion: latest,
|
|
243
|
-
reason: "npm install failed"
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
//#endregion
|
|
15
|
+
import { LogFileRotationTransport } from "@loglayer/transport-log-file-rotation";
|
|
16
|
+
import { LogLayer } from "loglayer";
|
|
17
|
+
import { serializeError } from "serialize-error";
|
|
247
18
|
//#region src/config.ts
|
|
248
19
|
const sudoPaneSchema = z.enum([
|
|
249
20
|
"allow",
|
|
250
21
|
"deny",
|
|
251
22
|
"hide"
|
|
252
23
|
]);
|
|
253
|
-
const completionNotificationModeSchema = z.enum([
|
|
254
|
-
"off",
|
|
255
|
-
"queue",
|
|
256
|
-
"toast",
|
|
257
|
-
"queue+toast",
|
|
258
|
-
"prompt"
|
|
259
|
-
]);
|
|
260
24
|
const configFilenames = ["opencode-zellij.config.jsonc", "opencode-zellij.config.json"];
|
|
261
25
|
const tabTitleLayerSchema = z.object({
|
|
262
26
|
enabled: z.boolean().optional().describe("Enable dynamic Zellij tab title updates."),
|
|
@@ -269,22 +33,12 @@ const tabTitleLayerSchema = z.object({
|
|
|
269
33
|
const ptyLayerSchema = z.object({
|
|
270
34
|
enabled: z.boolean().optional().describe("Enable Zellij-backed PTY tools."),
|
|
271
35
|
cleanupExitedPaneOnRead: z.boolean().optional().describe("Remove exited PTY panes after they are read."),
|
|
272
|
-
sudoPane: sudoPaneSchema.optional().describe("Controls whether the sudo pane tool is available, denied, or hidden.")
|
|
273
|
-
completionNotification: z.object({
|
|
274
|
-
mode: completionNotificationModeSchema.optional().describe("Controls how completion notifications are delivered."),
|
|
275
|
-
prompt: z.object({
|
|
276
|
-
requireIdle: z.boolean().optional().describe("Require the plugin to be idle before prompting."),
|
|
277
|
-
cooldownMs: z.number().finite().min(0).optional().describe("Cooldown time before prompting again in milliseconds."),
|
|
278
|
-
maxAttempts: z.number().finite().int().min(0).optional().describe("Maximum prompt attempts before backing off.")
|
|
279
|
-
}).strict().optional().describe("Prompt-specific completion notification settings.")
|
|
280
|
-
}).strict().optional().describe("Completion notification delivery settings.")
|
|
36
|
+
sudoPane: sudoPaneSchema.optional().describe("Controls whether the sudo pane tool is available, denied, or hidden.")
|
|
281
37
|
}).strict();
|
|
282
|
-
const autoUpdateLayerSchema = z.boolean().optional().describe("Enable automatic update checks for the opencode-zellij plugin.");
|
|
283
38
|
const sidecarConfigSchema = z.object({
|
|
284
39
|
$schema: z.string().optional().describe("JSON Schema URI for editor completion."),
|
|
285
40
|
tabTitle: tabTitleLayerSchema.optional(),
|
|
286
|
-
pty: ptyLayerSchema.optional()
|
|
287
|
-
autoUpdate: autoUpdateLayerSchema.optional()
|
|
41
|
+
pty: ptyLayerSchema.optional()
|
|
288
42
|
}).strict();
|
|
289
43
|
const defaultConfig = {
|
|
290
44
|
tabTitle: {
|
|
@@ -298,25 +52,15 @@ const defaultConfig = {
|
|
|
298
52
|
pty: {
|
|
299
53
|
enabled: true,
|
|
300
54
|
cleanupExitedPaneOnRead: true,
|
|
301
|
-
sudoPane: "allow"
|
|
302
|
-
|
|
303
|
-
mode: "queue+toast",
|
|
304
|
-
prompt: {
|
|
305
|
-
requireIdle: true,
|
|
306
|
-
cooldownMs: 3e4,
|
|
307
|
-
maxAttempts: 1
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
autoUpdate: true
|
|
55
|
+
sudoPane: "allow"
|
|
56
|
+
}
|
|
312
57
|
};
|
|
313
58
|
function validConfigLayer(value) {
|
|
314
59
|
const result = sidecarConfigSchema.safeParse(value);
|
|
315
60
|
if (!result.success) return void 0;
|
|
316
61
|
return {
|
|
317
62
|
tabTitle: result.data.tabTitle,
|
|
318
|
-
pty: result.data.pty
|
|
319
|
-
autoUpdate: result.data.autoUpdate
|
|
63
|
+
pty: result.data.pty
|
|
320
64
|
};
|
|
321
65
|
}
|
|
322
66
|
function mergeConfig(user, project) {
|
|
@@ -332,17 +76,8 @@ function mergeConfig(user, project) {
|
|
|
332
76
|
pty: {
|
|
333
77
|
enabled: project?.pty?.enabled ?? user?.pty?.enabled ?? defaultConfig.pty.enabled,
|
|
334
78
|
cleanupExitedPaneOnRead: project?.pty?.cleanupExitedPaneOnRead ?? user?.pty?.cleanupExitedPaneOnRead ?? defaultConfig.pty.cleanupExitedPaneOnRead,
|
|
335
|
-
sudoPane: project?.pty?.sudoPane ?? user?.pty?.sudoPane ?? defaultConfig.pty.sudoPane
|
|
336
|
-
|
|
337
|
-
mode: project?.pty?.completionNotification?.mode ?? user?.pty?.completionNotification?.mode ?? defaultConfig.pty.completionNotification.mode,
|
|
338
|
-
prompt: {
|
|
339
|
-
requireIdle: project?.pty?.completionNotification?.prompt?.requireIdle ?? user?.pty?.completionNotification?.prompt?.requireIdle ?? defaultConfig.pty.completionNotification.prompt.requireIdle,
|
|
340
|
-
cooldownMs: project?.pty?.completionNotification?.prompt?.cooldownMs ?? user?.pty?.completionNotification?.prompt?.cooldownMs ?? defaultConfig.pty.completionNotification.prompt.cooldownMs,
|
|
341
|
-
maxAttempts: project?.pty?.completionNotification?.prompt?.maxAttempts ?? user?.pty?.completionNotification?.prompt?.maxAttempts ?? defaultConfig.pty.completionNotification.prompt.maxAttempts
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
autoUpdate: project?.autoUpdate ?? user?.autoUpdate ?? defaultConfig.autoUpdate
|
|
79
|
+
sudoPane: project?.pty?.sudoPane ?? user?.pty?.sudoPane ?? defaultConfig.pty.sudoPane
|
|
80
|
+
}
|
|
346
81
|
};
|
|
347
82
|
}
|
|
348
83
|
async function loadConfigLayer(directory, warnings) {
|
|
@@ -490,8 +225,7 @@ var SessionManager = class {
|
|
|
490
225
|
reason: input.reason,
|
|
491
226
|
terminalAt: now,
|
|
492
227
|
tail: (input.tail ?? []).slice(-tombstoneTailLimit),
|
|
493
|
-
paneClosedAt: null
|
|
494
|
-
notificationSentAt: null
|
|
228
|
+
paneClosedAt: null
|
|
495
229
|
};
|
|
496
230
|
session.status = "terminal";
|
|
497
231
|
session.tombstone = tombstone;
|
|
@@ -521,16 +255,6 @@ var SessionManager = class {
|
|
|
521
255
|
}
|
|
522
256
|
return session;
|
|
523
257
|
}
|
|
524
|
-
markTerminalNotificationSent(id) {
|
|
525
|
-
const session = this.get(id);
|
|
526
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
527
|
-
if (!session.tombstone) return session;
|
|
528
|
-
if (!session.tombstone.notificationSentAt) {
|
|
529
|
-
session.tombstone.notificationSentAt = now;
|
|
530
|
-
session.updatedAt = now;
|
|
531
|
-
}
|
|
532
|
-
return session;
|
|
533
|
-
}
|
|
534
258
|
listByOpenCodeSession(openCodeSessionId) {
|
|
535
259
|
return this.list().filter((session) => session.openCodeSessionId === openCodeSessionId);
|
|
536
260
|
}
|
|
@@ -573,6 +297,17 @@ function buildCommandArgv(input, options = {}) {
|
|
|
573
297
|
];
|
|
574
298
|
}
|
|
575
299
|
//#endregion
|
|
300
|
+
//#region src/utils/debug.ts
|
|
301
|
+
function debug(message, ...details) {
|
|
302
|
+
if (!process.env.ZELLIJ_PTY_DEBUG) return;
|
|
303
|
+
console.warn(`[opencode-zellij] ${message}`, ...details);
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/utils/errors.ts
|
|
307
|
+
function errorMessage(error) {
|
|
308
|
+
return error instanceof Error ? error.message : String(error);
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
576
311
|
//#region src/zellij/parse.ts
|
|
577
312
|
function numericProperty(object, keys) {
|
|
578
313
|
for (const key of keys) {
|
|
@@ -1413,8 +1148,7 @@ function publicSession(session) {
|
|
|
1413
1148
|
reason: session.tombstone.reason,
|
|
1414
1149
|
terminalAt: session.tombstone.terminalAt,
|
|
1415
1150
|
tailLines: session.tombstone.tail.length,
|
|
1416
|
-
paneClosedAt: session.tombstone.paneClosedAt
|
|
1417
|
-
notificationSentAt: session.tombstone.notificationSentAt
|
|
1151
|
+
paneClosedAt: session.tombstone.paneClosedAt
|
|
1418
1152
|
} : null
|
|
1419
1153
|
};
|
|
1420
1154
|
}
|
|
@@ -1975,290 +1709,149 @@ const zellijPtyWriteTool = tool({
|
|
|
1975
1709
|
}
|
|
1976
1710
|
});
|
|
1977
1711
|
//#endregion
|
|
1978
|
-
//#region src/
|
|
1979
|
-
/**
|
|
1980
|
-
|
|
1712
|
+
//#region src/utils/runtime.ts
|
|
1713
|
+
/**
|
|
1714
|
+
* Detect whether the plugin process is running inside an OpenCode TUI
|
|
1715
|
+
* session, as opposed to a headless `opencode run` invocation.
|
|
1716
|
+
*
|
|
1717
|
+
* OpenCode spawns the TUI's renderer as a worker child process and
|
|
1718
|
+
* explicitly sets `OPENCODE_PROCESS_ROLE=worker`
|
|
1719
|
+
* (see `packages/opencode/src/cli/cmd/tui/thread.ts` in opencode). The
|
|
1720
|
+
* headless `opencode run` command keeps the default `main` role set by
|
|
1721
|
+
* the CLI entry point, so this is the most reliable signal to tell TUI
|
|
1722
|
+
* from headless.
|
|
1723
|
+
*
|
|
1724
|
+
* Outside the TUI there is no surface for toasts, prompts, or Zellij
|
|
1725
|
+
* panes, and the plugin's lifecycle hooks (watchdogs, tab title actor,
|
|
1726
|
+
* completion notifications) misbehave. The plugin short-circuits to a
|
|
1727
|
+
* no-op in headless mode to avoid leaking side effects.
|
|
1728
|
+
*/
|
|
1729
|
+
function isOpencodeTuiMode() {
|
|
1730
|
+
return process.env.OPENCODE_PROCESS_ROLE === "worker";
|
|
1731
|
+
}
|
|
1732
|
+
//#endregion
|
|
1733
|
+
//#region src/utils/logger.ts
|
|
1734
|
+
const defaultDebugLogPath = `${process.env.XDG_CACHE_HOME?.trim() || `${homedir()}/.cache`}/opencode-zellij/debug.log`;
|
|
1735
|
+
function buildRootLogger() {
|
|
1736
|
+
const filename = process.env.OPENCODE_ZELLIJ_DEBUG_LOG?.trim() || defaultDebugLogPath;
|
|
1737
|
+
if (!filename) return null;
|
|
1981
1738
|
try {
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
}
|
|
2001
|
-
snapshot[sessionID] = parsed;
|
|
2002
|
-
}
|
|
2003
|
-
return snapshot;
|
|
2004
|
-
} catch (err) {
|
|
2005
|
-
debug("fetchPromptIdleStatusSnapshot failed", errorMessage(err));
|
|
2006
|
-
return;
|
|
1739
|
+
mkdirSync(dirname(filename), { recursive: true });
|
|
1740
|
+
} catch {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
return new LogLayer({
|
|
1745
|
+
errorSerializer: serializeError,
|
|
1746
|
+
transport: new LogFileRotationTransport({
|
|
1747
|
+
filename: filename.includes("%DATE%") ? filename : `${filename}-%DATE%`,
|
|
1748
|
+
dateFormat: "YMD",
|
|
1749
|
+
size: "1M",
|
|
1750
|
+
maxLogs: "7d",
|
|
1751
|
+
frequency: "daily",
|
|
1752
|
+
compressOnRotate: true
|
|
1753
|
+
})
|
|
1754
|
+
});
|
|
1755
|
+
} catch {
|
|
1756
|
+
return null;
|
|
2007
1757
|
}
|
|
2008
1758
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
message: typeof status.message === "string" ? status.message : "",
|
|
2017
|
-
next: typeof status.next === "number" ? status.next : 0
|
|
2018
|
-
};
|
|
1759
|
+
const rootLogger = buildRootLogger();
|
|
1760
|
+
/**
|
|
1761
|
+
* Returns a child logger that prefixes every message with `[name]`, or
|
|
1762
|
+
* null if the root logger failed to initialize.
|
|
1763
|
+
*/
|
|
1764
|
+
function getChildLogger(name) {
|
|
1765
|
+
return rootLogger?.withPrefix(`[${name}]`) ?? null;
|
|
2019
1766
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
const
|
|
2023
|
-
function
|
|
2024
|
-
|
|
1767
|
+
//#endregion
|
|
1768
|
+
//#region src/zellij/completion-notifications.ts
|
|
1769
|
+
const logger = getChildLogger("completion-notifications");
|
|
1770
|
+
async function postPromptAsync(serverUrl, sessionID, body) {
|
|
1771
|
+
const url = new URL(`/session/${encodeURIComponent(sessionID)}/prompt_async`, serverUrl);
|
|
1772
|
+
const headers = { "Content-Type": "application/json" };
|
|
1773
|
+
const directory = process.env.OPENCODE_DIRECTORY?.trim();
|
|
1774
|
+
if (directory) headers["x-opencode-directory"] = encodeURIComponent(directory);
|
|
1775
|
+
return fetch(url, {
|
|
1776
|
+
method: "POST",
|
|
1777
|
+
headers,
|
|
1778
|
+
body: JSON.stringify(body)
|
|
1779
|
+
});
|
|
2025
1780
|
}
|
|
2026
|
-
function
|
|
2027
|
-
return
|
|
1781
|
+
function formatExitCode(exitCode) {
|
|
1782
|
+
return exitCode === null ? "?" : String(exitCode);
|
|
1783
|
+
}
|
|
1784
|
+
function buildCompletionPromptText(event) {
|
|
1785
|
+
const { paneId, exitCode } = event.session;
|
|
1786
|
+
return `[zellij_pty] pane ${paneId} exit=${formatExitCode(exitCode)} — call zellij_pty_read to read, then zellij_pty_kill to close.`;
|
|
2028
1787
|
}
|
|
2029
1788
|
function buildCompletionPromptRequest(event) {
|
|
2030
1789
|
return {
|
|
2031
|
-
|
|
2032
|
-
body: { parts: [{
|
|
2033
|
-
type: "text",
|
|
2034
|
-
text: completionMessage
|
|
2035
|
-
}] }
|
|
2036
|
-
};
|
|
2037
|
-
}
|
|
2038
|
-
function injectQueuedCompletionNotice(input, notice) {
|
|
2039
|
-
if (typeof input === "string") return `${notice}\n\n${input}`;
|
|
2040
|
-
if (!input || typeof input !== "object") return input;
|
|
2041
|
-
const record = input;
|
|
2042
|
-
if (Array.isArray(record.parts)) return {
|
|
2043
|
-
...record,
|
|
1790
|
+
sessionID: event.session.openCodeSessionId,
|
|
2044
1791
|
parts: [{
|
|
2045
1792
|
type: "text",
|
|
2046
|
-
text:
|
|
2047
|
-
}
|
|
2048
|
-
};
|
|
2049
|
-
if (typeof record.message === "string") return {
|
|
2050
|
-
...record,
|
|
2051
|
-
message: `${notice}\n\n${record.message}`
|
|
2052
|
-
};
|
|
2053
|
-
if (typeof record.content === "string") return {
|
|
2054
|
-
...record,
|
|
2055
|
-
content: `${notice}\n\n${record.content}`
|
|
2056
|
-
};
|
|
2057
|
-
if (typeof record.text === "string") return {
|
|
2058
|
-
...record,
|
|
2059
|
-
text: `${notice}\n\n${record.text}`
|
|
2060
|
-
};
|
|
2061
|
-
return {
|
|
2062
|
-
...record,
|
|
2063
|
-
message: notice
|
|
2064
|
-
};
|
|
2065
|
-
}
|
|
2066
|
-
function evaluateCompletionPromptDecision(input) {
|
|
2067
|
-
if (!supportsActivePrompt(input.config.mode)) return {
|
|
2068
|
-
shouldPrompt: false,
|
|
2069
|
-
shouldQueue: false,
|
|
2070
|
-
reason: "prompt mode disabled"
|
|
2071
|
-
};
|
|
2072
|
-
if (input.event.session.humanInputOnly || !input.event.session.allowAgentInput) return {
|
|
2073
|
-
shouldPrompt: false,
|
|
2074
|
-
shouldQueue: true,
|
|
2075
|
-
reason: "human-input-only session"
|
|
2076
|
-
};
|
|
2077
|
-
if (!input.promptClientAvailable) return {
|
|
2078
|
-
shouldPrompt: false,
|
|
2079
|
-
shouldQueue: true,
|
|
2080
|
-
reason: "prompt client unavailable"
|
|
2081
|
-
};
|
|
2082
|
-
if (!input.event.session.openCodeSessionId) return {
|
|
2083
|
-
shouldPrompt: false,
|
|
2084
|
-
shouldQueue: true,
|
|
2085
|
-
reason: "session id unavailable"
|
|
2086
|
-
};
|
|
2087
|
-
if (!input.snapshotAvailable) return {
|
|
2088
|
-
shouldPrompt: false,
|
|
2089
|
-
shouldQueue: true,
|
|
2090
|
-
reason: "session status snapshot unavailable"
|
|
2091
|
-
};
|
|
2092
|
-
if (input.config.prompt.maxAttempts <= 0 || input.promptAttemptCount >= input.config.prompt.maxAttempts) return {
|
|
2093
|
-
shouldPrompt: false,
|
|
2094
|
-
shouldQueue: true,
|
|
2095
|
-
reason: "prompt max attempts reached"
|
|
2096
|
-
};
|
|
2097
|
-
if (input.config.prompt.cooldownMs > 0 && input.lastPromptAttemptAt !== null && input.now - input.lastPromptAttemptAt < input.config.prompt.cooldownMs) return {
|
|
2098
|
-
shouldPrompt: false,
|
|
2099
|
-
shouldQueue: true,
|
|
2100
|
-
reason: "prompt cooldown active"
|
|
2101
|
-
};
|
|
2102
|
-
if (input.config.prompt.requireIdle) {
|
|
2103
|
-
const sessionId = input.event.session.openCodeSessionId;
|
|
2104
|
-
if (!sessionId) return {
|
|
2105
|
-
shouldPrompt: false,
|
|
2106
|
-
shouldQueue: true,
|
|
2107
|
-
reason: "session status unavailable"
|
|
2108
|
-
};
|
|
2109
|
-
const status = sessionId ? input.snapshot?.[sessionId] : void 0;
|
|
2110
|
-
if (!status) return {
|
|
2111
|
-
shouldPrompt: false,
|
|
2112
|
-
shouldQueue: true,
|
|
2113
|
-
reason: "session status unavailable"
|
|
2114
|
-
};
|
|
2115
|
-
if (status && status.type !== "idle") return {
|
|
2116
|
-
shouldPrompt: false,
|
|
2117
|
-
shouldQueue: true,
|
|
2118
|
-
reason: "session not idle"
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
|
-
return {
|
|
2122
|
-
shouldPrompt: true,
|
|
2123
|
-
shouldQueue: false,
|
|
2124
|
-
reason: "prompt allowed"
|
|
1793
|
+
text: buildCompletionPromptText(event)
|
|
1794
|
+
}]
|
|
2125
1795
|
};
|
|
2126
1796
|
}
|
|
2127
|
-
var
|
|
2128
|
-
|
|
2129
|
-
constructor(context
|
|
1797
|
+
var SessionCompletionNotificationManager = class {
|
|
1798
|
+
seen = /* @__PURE__ */ new Set();
|
|
1799
|
+
constructor(context) {
|
|
2130
1800
|
this.context = context;
|
|
2131
|
-
this.hooks = hooks;
|
|
2132
|
-
this.clock = clock;
|
|
2133
|
-
}
|
|
2134
|
-
hasPending(sessionId) {
|
|
2135
|
-
return this.states.get(sessionId)?.queued ?? false;
|
|
2136
|
-
}
|
|
2137
|
-
clearSession(sessionId) {
|
|
2138
|
-
this.states.delete(sessionId);
|
|
2139
|
-
}
|
|
2140
|
-
clearAll() {
|
|
2141
|
-
this.states.clear();
|
|
2142
1801
|
}
|
|
2143
1802
|
dispose() {
|
|
2144
|
-
this.
|
|
1803
|
+
this.seen.clear();
|
|
2145
1804
|
}
|
|
2146
1805
|
async handleSessionTerminal(event) {
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
event,
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
this.states.set(event.sessionId, state);
|
|
2158
|
-
switch (this.context.config.mode) {
|
|
2159
|
-
case "queue":
|
|
2160
|
-
state.queued = true;
|
|
2161
|
-
return;
|
|
2162
|
-
case "toast":
|
|
2163
|
-
await this.sendToast(state);
|
|
2164
|
-
this.finalize(state);
|
|
2165
|
-
return;
|
|
2166
|
-
case "queue+toast":
|
|
2167
|
-
await this.tryPromptOrQueue(state);
|
|
2168
|
-
await this.sendToast(state);
|
|
2169
|
-
return;
|
|
2170
|
-
case "prompt":
|
|
2171
|
-
await this.tryPromptOrQueue(state);
|
|
2172
|
-
break;
|
|
2173
|
-
default:
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
injectQueuedChatMessage(input) {
|
|
2177
|
-
const pending = [...this.states.values()].filter((state) => state.queued);
|
|
2178
|
-
if (pending.length === 0) return input;
|
|
2179
|
-
const notice = buildQueuedCompletionNotice(pending.map((state) => state.event));
|
|
2180
|
-
for (const state of pending) {
|
|
2181
|
-
this.markStateSent(state);
|
|
2182
|
-
this.finalize(state);
|
|
2183
|
-
}
|
|
2184
|
-
return injectQueuedCompletionNotice(input, notice);
|
|
2185
|
-
}
|
|
2186
|
-
async tryPromptOrQueue(state) {
|
|
2187
|
-
if (!state.event.session.openCodeSessionId) {
|
|
2188
|
-
state.queued = true;
|
|
1806
|
+
logger?.withMetadata({
|
|
1807
|
+
session: event.sessionId,
|
|
1808
|
+
reason: event.reason,
|
|
1809
|
+
paneId: event.session.paneId,
|
|
1810
|
+
openCodeSessionId: event.session.openCodeSessionId ?? "null"
|
|
1811
|
+
}).info("handleSessionTerminal");
|
|
1812
|
+
if (this.seen.has(event.sessionId)) return;
|
|
1813
|
+
this.seen.add(event.sessionId);
|
|
1814
|
+
if (!event.session.openCodeSessionId) {
|
|
1815
|
+
logger?.withMetadata({ session: event.sessionId }).info("skipped: no openCodeSessionId");
|
|
2189
1816
|
return;
|
|
2190
1817
|
}
|
|
2191
1818
|
const session = this.context.client.session;
|
|
2192
|
-
const
|
|
2193
|
-
const
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
now: this.clock(),
|
|
2200
|
-
lastPromptAttemptAt: state.promptAttemptedAt,
|
|
2201
|
-
promptAttemptCount: state.promptAttempts,
|
|
2202
|
-
promptClientAvailable: Boolean(prompt)
|
|
2203
|
-
});
|
|
2204
|
-
if (!decision.shouldPrompt) {
|
|
2205
|
-
state.queued = decision.shouldQueue;
|
|
1819
|
+
const sdkPrompt = session?.promptAsync ?? session?.prompt;
|
|
1820
|
+
const request = buildCompletionPromptRequest(event);
|
|
1821
|
+
const sessionID = request.sessionID;
|
|
1822
|
+
if (sdkPrompt) try {
|
|
1823
|
+
logger?.withMetadata({ sessionID }).info("SDK prompt attempt");
|
|
1824
|
+
await sdkPrompt(request);
|
|
1825
|
+
logger?.withMetadata({ sessionID }).info("SDK prompt ok");
|
|
2206
1826
|
return;
|
|
2207
|
-
}
|
|
2208
|
-
if (this.hooks.prompt) try {
|
|
2209
|
-
const maybePromise = this.hooks.prompt(state.event);
|
|
2210
|
-
if (maybePromise && typeof maybePromise.then === "function") await maybePromise;
|
|
2211
1827
|
} catch (error) {
|
|
2212
|
-
|
|
1828
|
+
logger?.withMetadata({
|
|
1829
|
+
sessionID,
|
|
1830
|
+
error: errorMessage(error)
|
|
1831
|
+
}).warn("SDK prompt failed, falling back to HTTP");
|
|
2213
1832
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
1833
|
+
else logger?.withMetadata({
|
|
1834
|
+
sessionID,
|
|
1835
|
+
sessionKeys: session ? Object.keys(session).join(",") : "null"
|
|
1836
|
+
}).info("no SDK prompt on client, using HTTP fallback");
|
|
1837
|
+
if (!this.context.serverUrl) {
|
|
1838
|
+
logger?.withMetadata({ sessionID }).error("no serverUrl for HTTP fallback");
|
|
2216
1839
|
return;
|
|
2217
1840
|
}
|
|
2218
|
-
state.promptAttempts += 1;
|
|
2219
|
-
state.promptAttemptedAt = this.clock();
|
|
2220
1841
|
try {
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
}
|
|
2227
|
-
this.markStateSent(state);
|
|
2228
|
-
this.finalize(state);
|
|
1842
|
+
const response = await postPromptAsync(this.context.serverUrl, sessionID, { parts: request.parts });
|
|
1843
|
+
logger?.withMetadata({
|
|
1844
|
+
sessionID,
|
|
1845
|
+
status: response.status,
|
|
1846
|
+
ok: response.ok
|
|
1847
|
+
}).info("HTTP fallback response");
|
|
2229
1848
|
} catch (error) {
|
|
2230
|
-
|
|
2231
|
-
|
|
1849
|
+
logger?.withMetadata({
|
|
1850
|
+
sessionID,
|
|
1851
|
+
error: errorMessage(error)
|
|
1852
|
+
}).error("HTTP fallback threw");
|
|
2232
1853
|
}
|
|
2233
1854
|
}
|
|
2234
|
-
async sendToast(state) {
|
|
2235
|
-
const toast = this.context.client.tui?.showToast;
|
|
2236
|
-
if (!toast) {
|
|
2237
|
-
debug("completion notification toast skipped: client.tui.showToast unavailable");
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
|
-
try {
|
|
2241
|
-
await toast({ body: {
|
|
2242
|
-
title: completionTitle,
|
|
2243
|
-
message: completionMessage,
|
|
2244
|
-
variant: "success",
|
|
2245
|
-
duration: 1e4
|
|
2246
|
-
} });
|
|
2247
|
-
state.toastSent = true;
|
|
2248
|
-
this.markStateSent(state);
|
|
2249
|
-
if (!state.queued) this.finalize(state);
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
debug("completion notification toast failed", errorMessage(error));
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
markStateSent(state) {
|
|
2255
|
-
if (state.sent) return;
|
|
2256
|
-
this.context.markSent(state.event.sessionId);
|
|
2257
|
-
state.sent = true;
|
|
2258
|
-
}
|
|
2259
|
-
finalize(state) {
|
|
2260
|
-
this.states.delete(state.event.sessionId);
|
|
2261
|
-
}
|
|
2262
1855
|
};
|
|
2263
1856
|
//#endregion
|
|
2264
1857
|
//#region src/zellij/shutdown-cleanup.ts
|
|
@@ -2600,9 +2193,6 @@ var TabTitleManager = class {
|
|
|
2600
2193
|
emojis;
|
|
2601
2194
|
enabled;
|
|
2602
2195
|
destroyed = false;
|
|
2603
|
-
originalTabTitle;
|
|
2604
|
-
originalTabTitleLoaded = false;
|
|
2605
|
-
originalTabTitlePromise;
|
|
2606
2196
|
destroyPromise;
|
|
2607
2197
|
actor;
|
|
2608
2198
|
constructor(options) {
|
|
@@ -2628,8 +2218,6 @@ var TabTitleManager = class {
|
|
|
2628
2218
|
}
|
|
2629
2219
|
async renderImmediate() {
|
|
2630
2220
|
if (!this.enabled || this.destroyed) return;
|
|
2631
|
-
await this.ensureOriginalTabTitle();
|
|
2632
|
-
if (this.destroyed) return;
|
|
2633
2221
|
this.desiredTitle = this.buildTitle();
|
|
2634
2222
|
this.clearDebounceTimer();
|
|
2635
2223
|
await this.syncDesiredTitle();
|
|
@@ -2651,7 +2239,6 @@ var TabTitleManager = class {
|
|
|
2651
2239
|
async syncDesiredTitle() {
|
|
2652
2240
|
if (!this.enabled || this.destroyed) return;
|
|
2653
2241
|
const generation = this.syncGeneration;
|
|
2654
|
-
await this.ensureOriginalTabTitle();
|
|
2655
2242
|
if (this.destroyed || generation !== this.syncGeneration) return;
|
|
2656
2243
|
if (this.syncInFlight) return this.syncPromise;
|
|
2657
2244
|
this.syncInFlight = true;
|
|
@@ -2701,23 +2288,6 @@ var TabTitleManager = class {
|
|
|
2701
2288
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
2702
2289
|
this.debounceTimer = void 0;
|
|
2703
2290
|
}
|
|
2704
|
-
async ensureOriginalTabTitle() {
|
|
2705
|
-
if (!this.enabled || this.originalTabTitleLoaded) return;
|
|
2706
|
-
if (this.originalTabTitlePromise) return this.originalTabTitlePromise;
|
|
2707
|
-
this.originalTabTitlePromise = this.saveOriginalTabTitle();
|
|
2708
|
-
return this.originalTabTitlePromise;
|
|
2709
|
-
}
|
|
2710
|
-
async saveOriginalTabTitle() {
|
|
2711
|
-
try {
|
|
2712
|
-
const title = await this.cli.currentTabTitle();
|
|
2713
|
-
if (title !== void 0) this.originalTabTitle = title;
|
|
2714
|
-
} catch (error) {
|
|
2715
|
-
debug("TabTitleManager failed to save original tab title", errorMessage(error));
|
|
2716
|
-
} finally {
|
|
2717
|
-
this.originalTabTitleLoaded = true;
|
|
2718
|
-
this.originalTabTitlePromise = void 0;
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
2291
|
destroy() {
|
|
2722
2292
|
if (this.destroyed) return this.destroyPromise ?? Promise.resolve();
|
|
2723
2293
|
this.destroyed = true;
|
|
@@ -2725,21 +2295,12 @@ var TabTitleManager = class {
|
|
|
2725
2295
|
this.desiredTitle = void 0;
|
|
2726
2296
|
this.clearDebounceTimer();
|
|
2727
2297
|
this.clearRetryTimer();
|
|
2728
|
-
|
|
2729
|
-
this.destroyPromise = this.restoreOriginalTabTitle().catch((error) => debug("TabTitleManager failed to restore original tab title", errorMessage(error)));
|
|
2730
|
-
return this.destroyPromise;
|
|
2731
|
-
}
|
|
2732
|
-
async restoreOriginalTabTitle() {
|
|
2733
|
-
await this.originalTabTitlePromise;
|
|
2734
|
-
await this.syncPromise;
|
|
2735
|
-
const originalTitle = this.originalTabTitle;
|
|
2736
|
-
this.originalTabTitle = void 0;
|
|
2737
|
-
if (originalTitle === void 0) return;
|
|
2738
|
-
await this.cli.renameTab(originalTitle);
|
|
2298
|
+
return Promise.resolve();
|
|
2739
2299
|
}
|
|
2740
2300
|
};
|
|
2741
2301
|
//#endregion
|
|
2742
2302
|
//#region src/plugin.ts
|
|
2303
|
+
const PLUGIN_ID = "opencode-zellij";
|
|
2743
2304
|
function createPtyTools(defaultCleanupExitedPaneOnRead) {
|
|
2744
2305
|
return {
|
|
2745
2306
|
zellij_pty_spawn: zellijPtySpawnTool,
|
|
@@ -2755,29 +2316,6 @@ function getProjectName(path) {
|
|
|
2755
2316
|
function getWorkspaceRoot(input) {
|
|
2756
2317
|
return input.worktree || input.directory || process.cwd();
|
|
2757
2318
|
}
|
|
2758
|
-
function showUpdateToast(client, result) {
|
|
2759
|
-
if (result.type === "updated") client.tui.showToast({ body: {
|
|
2760
|
-
title: "opencode-zellij updated",
|
|
2761
|
-
message: `Updated to ${result.toVersion}. Restart OpenCode to apply the changes.`,
|
|
2762
|
-
variant: "success",
|
|
2763
|
-
duration: 1e4
|
|
2764
|
-
} }).catch((error) => debug("show update toast for successful update failed", errorMessage(error)));
|
|
2765
|
-
else if (result.type === "failed") client.tui.showToast({ body: {
|
|
2766
|
-
title: "opencode-zellij update failed",
|
|
2767
|
-
message: `Failed to update to ${result.latestVersion}.`,
|
|
2768
|
-
variant: "error",
|
|
2769
|
-
duration: 8e3
|
|
2770
|
-
} }).catch((error) => debug("show update toast for failed update failed", errorMessage(error)));
|
|
2771
|
-
}
|
|
2772
|
-
function startAutoUpdateCheck(client, importMetaUrl, check = checkAndUpdate) {
|
|
2773
|
-
(async () => {
|
|
2774
|
-
try {
|
|
2775
|
-
showUpdateToast(client, await check({ importMetaUrl }));
|
|
2776
|
-
} catch (cause) {
|
|
2777
|
-
debug("auto-update check failed", errorMessage(cause));
|
|
2778
|
-
}
|
|
2779
|
-
})();
|
|
2780
|
-
}
|
|
2781
2319
|
async function cleanupStep(stepName, sessionId, step) {
|
|
2782
2320
|
try {
|
|
2783
2321
|
await step();
|
|
@@ -2793,6 +2331,10 @@ async function cleanupDeletedSession(sessionId) {
|
|
|
2793
2331
|
}
|
|
2794
2332
|
function createZellijPtyPlugin(dependencies = {}) {
|
|
2795
2333
|
return async (input) => {
|
|
2334
|
+
if (!isOpencodeTuiMode()) {
|
|
2335
|
+
debug("opencode-zellij disabled: not running inside an OpenCode TUI session");
|
|
2336
|
+
return {};
|
|
2337
|
+
}
|
|
2796
2338
|
const { config, warnings } = await loadConfig(input);
|
|
2797
2339
|
for (const warning of warnings) debug(warning);
|
|
2798
2340
|
configureSudoPane(config.pty.sudoPane === "allow");
|
|
@@ -2820,60 +2362,35 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2820
2362
|
branch: config.tabTitle.emojiBranch
|
|
2821
2363
|
}
|
|
2822
2364
|
}) : void 0;
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
try {
|
|
2830
|
-
sessionManager.markTerminalNotificationSent(sessionId);
|
|
2831
|
-
} catch (error) {
|
|
2832
|
-
debug("mark terminal notification sent failed", errorMessage(error));
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
}) ?? new SessionCompletionNotificationQueue({
|
|
2836
|
-
client,
|
|
2837
|
-
workspaceRoot,
|
|
2838
|
-
config: config.pty.completionNotification,
|
|
2839
|
-
markSent(sessionId) {
|
|
2840
|
-
try {
|
|
2841
|
-
sessionManager.markTerminalNotificationSent(sessionId);
|
|
2842
|
-
} catch (error) {
|
|
2843
|
-
debug("mark terminal notification sent failed", errorMessage(error));
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2365
|
+
const completionNotifications = dependencies.createCompletionNotifications?.({
|
|
2366
|
+
client: { session: input.client?.session },
|
|
2367
|
+
serverUrl: input.serverUrl
|
|
2368
|
+
}) ?? new SessionCompletionNotificationManager({
|
|
2369
|
+
client: { session: input.client?.session },
|
|
2370
|
+
serverUrl: input.serverUrl
|
|
2846
2371
|
});
|
|
2847
|
-
subscriberManager.setLifecycleHooks(
|
|
2372
|
+
subscriberManager.setLifecycleHooks({ onSessionTerminal: (event) => void completionNotifications.handleSessionTerminal(event).catch((error) => debug("completion notification lifecycle hook failed", errorMessage(error))) });
|
|
2848
2373
|
if (actor) await actor.ready;
|
|
2849
2374
|
tabTitleManager?.renderImmediate().catch((error) => debug("initial tab title render failed", errorMessage(error)));
|
|
2850
|
-
if (config.autoUpdate) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
|
|
2851
2375
|
return {
|
|
2852
2376
|
async event(input) {
|
|
2853
2377
|
const event = input.event;
|
|
2854
2378
|
if (actor && tabTitleManager) {
|
|
2855
2379
|
await actor.handleEvent(event);
|
|
2856
|
-
|
|
2857
|
-
else tabTitleManager.scheduleUpdate();
|
|
2380
|
+
tabTitleManager.scheduleUpdate();
|
|
2858
2381
|
}
|
|
2859
2382
|
if (event.type === "server.instance.disposed" || event.type === "global.disposed") {
|
|
2860
|
-
completionNotifications
|
|
2861
|
-
completionNotifications?.dispose();
|
|
2383
|
+
completionNotifications.dispose();
|
|
2862
2384
|
subscriberManager.setLifecycleHooks(void 0);
|
|
2863
2385
|
}
|
|
2864
2386
|
if (event.type === "session.deleted") {
|
|
2865
2387
|
const sessionID = deletedSessionID$1(event);
|
|
2866
2388
|
if (!sessionID) return;
|
|
2867
2389
|
const sessions = sessionManager.listByOpenCodeSession(sessionID);
|
|
2868
|
-
for (const session of sessions) completionNotifications?.clearSession(session.id);
|
|
2869
2390
|
await Promise.all(sessions.map((session) => cleanupDeletedSession(session.id)));
|
|
2870
2391
|
}
|
|
2871
2392
|
},
|
|
2872
|
-
|
|
2873
|
-
const injected = completionNotifications?.injectQueuedChatMessage(output) ?? output;
|
|
2874
|
-
if (injected !== output && injected && typeof injected === "object" && Array.isArray(injected.parts)) output.parts = injected.parts;
|
|
2875
|
-
},
|
|
2876
|
-
"tool": config.pty.enabled ? {
|
|
2393
|
+
tool: config.pty.enabled ? {
|
|
2877
2394
|
...createPtyTools(config.pty.cleanupExitedPaneOnRead),
|
|
2878
2395
|
...config.pty.sudoPane === "hide" ? {} : { zellij_pty_request_sudo: requestSudoTool }
|
|
2879
2396
|
} : {}
|
|
@@ -2881,7 +2398,11 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2881
2398
|
};
|
|
2882
2399
|
}
|
|
2883
2400
|
const ZellijPtyPlugin = createZellijPtyPlugin();
|
|
2401
|
+
var plugin_default = {
|
|
2402
|
+
id: PLUGIN_ID,
|
|
2403
|
+
server: ZellijPtyPlugin
|
|
2404
|
+
};
|
|
2884
2405
|
//#endregion
|
|
2885
|
-
export { ZellijPtyPlugin,
|
|
2406
|
+
export { ZellijPtyPlugin, createZellijPtyPlugin, plugin_default as default };
|
|
2886
2407
|
|
|
2887
2408
|
//# sourceMappingURL=index.mjs.map
|