opencode-zellij 0.0.12 → 0.0.14
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/dist/index.mjs +273 -301
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/index.mjs
CHANGED
|
@@ -39,7 +39,7 @@ async function installedPackageMetadata(installRoot) {
|
|
|
39
39
|
try {
|
|
40
40
|
const content = await readFile(join(packageDir(installRoot), "package.json"), "utf8");
|
|
41
41
|
const pkg = JSON.parse(content);
|
|
42
|
-
if (isRecord$
|
|
42
|
+
if (isRecord$2(pkg)) return {
|
|
43
43
|
name: typeof pkg.name === "string" ? pkg.name : void 0,
|
|
44
44
|
version: typeof pkg.version === "string" ? pkg.version : void 0,
|
|
45
45
|
main: typeof pkg.main === "string" ? pkg.main : void 0
|
|
@@ -107,7 +107,7 @@ async function findInstallContext(importMetaUrl) {
|
|
|
107
107
|
try {
|
|
108
108
|
const content = await readFile(packageJsonPath, "utf8");
|
|
109
109
|
const pkg = JSON.parse(content);
|
|
110
|
-
if (isRecord$
|
|
110
|
+
if (isRecord$2(pkg) && pkg.name === "opencode-zellij" && typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
111
111
|
const installRoot = dirname(dirname(dir));
|
|
112
112
|
if (existsSync(join(installRoot, "package.json"))) return {
|
|
113
113
|
installRoot,
|
|
@@ -124,7 +124,7 @@ async function findInstallContext(importMetaUrl) {
|
|
|
124
124
|
dir = parent;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
function isRecord$
|
|
127
|
+
function isRecord$2(value) {
|
|
128
128
|
return typeof value === "object" && value !== null;
|
|
129
129
|
}
|
|
130
130
|
function isAutoUpdatableSpec(spec) {
|
|
@@ -143,7 +143,7 @@ async function fetchLatestVersion(fetchImpl = globalThis.fetch) {
|
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
const data = await response.json();
|
|
146
|
-
if (isRecord$
|
|
146
|
+
if (isRecord$2(data) && typeof data.latest === "string") return data.latest;
|
|
147
147
|
debug("npm registry response missing latest tag");
|
|
148
148
|
return;
|
|
149
149
|
} catch (cause) {
|
|
@@ -584,7 +584,7 @@ function numericProperty(object, keys) {
|
|
|
584
584
|
}
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
|
-
function stringProperty$
|
|
587
|
+
function stringProperty$2(object, keys) {
|
|
588
588
|
for (const key of keys) {
|
|
589
589
|
const value = object[key];
|
|
590
590
|
if (typeof value === "string") return value;
|
|
@@ -643,7 +643,7 @@ function parsePaneExists(listPanesJson, paneId) {
|
|
|
643
643
|
function tabNameProperty(object, tabId) {
|
|
644
644
|
if (tabId === void 0) return void 0;
|
|
645
645
|
if (numericProperty(object, ["tab_id", "tabId"]) !== tabId) return void 0;
|
|
646
|
-
const name = stringProperty$
|
|
646
|
+
const name = stringProperty$2(object, ["name", "title"]);
|
|
647
647
|
return typeof name === "string" ? name : void 0;
|
|
648
648
|
}
|
|
649
649
|
function findTabName(value, tabId) {
|
|
@@ -1913,62 +1913,18 @@ const zellijPtyWriteTool = tool({
|
|
|
1913
1913
|
}
|
|
1914
1914
|
});
|
|
1915
1915
|
//#endregion
|
|
1916
|
-
//#region src/zellij/
|
|
1917
|
-
/**
|
|
1918
|
-
|
|
1919
|
-
* via the `/session/status` snapshot API.
|
|
1920
|
-
*
|
|
1921
|
-
* Base state (running vs idle) is sourced from the snapshot rather than
|
|
1922
|
-
* individual `session.idle` events because testing showed that both parent and
|
|
1923
|
-
* child sessions report busy during subagent execution, and the parent remains
|
|
1924
|
-
* busy even after the child completes. The snapshot gives a consistent,
|
|
1925
|
-
* server-authoritative view. `needs-input` has no REST API, so it continues
|
|
1926
|
-
* to be managed purely through events.
|
|
1927
|
-
*
|
|
1928
|
-
* The snapshot reconciliation is intentionally *not* optimistic for idle-like
|
|
1929
|
-
* transitions: a lone parent/child idle event can be stale during subagent
|
|
1930
|
-
* handoff. The debounce coalesces high-frequency event streams to avoid
|
|
1931
|
-
* hammering the API on every individual status change.
|
|
1932
|
-
*/
|
|
1933
|
-
function shouldRefreshTabTitleStatusSnapshot(event) {
|
|
1934
|
-
switch (event.type) {
|
|
1935
|
-
case "session.status":
|
|
1936
|
-
case "session.idle":
|
|
1937
|
-
case "session.error":
|
|
1938
|
-
case "session.created":
|
|
1939
|
-
case "session.deleted":
|
|
1940
|
-
case "question.asked":
|
|
1941
|
-
case "question.replied":
|
|
1942
|
-
case "question.rejected":
|
|
1943
|
-
case "permission.asked":
|
|
1944
|
-
case "permission.replied":
|
|
1945
|
-
case "permission.updated": return true;
|
|
1946
|
-
default: return false;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
/**
|
|
1950
|
-
* Best-effort fetch of session statuses for a workspace.
|
|
1951
|
-
*
|
|
1952
|
-
* Only accepts object-keyed maps: `{ [sessionID]: SessionStatus }` directly,
|
|
1953
|
-
* or the generated-client envelope `{ data: { [sessionID]: SessionStatus } }`.
|
|
1954
|
-
*
|
|
1955
|
-
* An empty map `{}` is a valid snapshot (all sessions ended / none tracked).
|
|
1956
|
-
* Arrays are never accepted — they always return undefined.
|
|
1957
|
-
* If any single status entry fails to parse, the entire snapshot is rejected
|
|
1958
|
-
* (no partial apply) to avoid incorrectly clearing session states.
|
|
1959
|
-
*
|
|
1960
|
-
* Failures are swallowed and return undefined so the caller never throws.
|
|
1961
|
-
*/
|
|
1962
|
-
async function fetchSessionStatusSnapshot(client, workspaceRoot) {
|
|
1916
|
+
//#region src/zellij/completion-notifications.ts
|
|
1917
|
+
/** Fetches the prompt-mode idle guard status; unrelated to tab title state. */
|
|
1918
|
+
async function fetchPromptIdleStatusSnapshot(client, workspaceRoot) {
|
|
1963
1919
|
try {
|
|
1964
1920
|
if (!client.session?.status) {
|
|
1965
|
-
debug("
|
|
1921
|
+
debug("fetchPromptIdleStatusSnapshot: client.session.status not available");
|
|
1966
1922
|
return;
|
|
1967
1923
|
}
|
|
1968
1924
|
const result = await client.session.status({ query: { directory: workspaceRoot } });
|
|
1969
1925
|
const payload = result && typeof result === "object" && "data" in result ? result.data : result;
|
|
1970
1926
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1971
|
-
debug("
|
|
1927
|
+
debug("fetchPromptIdleStatusSnapshot received non-object payload");
|
|
1972
1928
|
return;
|
|
1973
1929
|
}
|
|
1974
1930
|
const entries = Object.entries(payload);
|
|
@@ -1977,14 +1933,14 @@ async function fetchSessionStatusSnapshot(client, workspaceRoot) {
|
|
|
1977
1933
|
for (const [sessionID, status] of entries) {
|
|
1978
1934
|
const parsed = parseSessionStatus(status);
|
|
1979
1935
|
if (parsed === void 0) {
|
|
1980
|
-
debug("
|
|
1936
|
+
debug("fetchPromptIdleStatusSnapshot received invalid status entry, rejecting entire snapshot");
|
|
1981
1937
|
return;
|
|
1982
1938
|
}
|
|
1983
1939
|
snapshot[sessionID] = parsed;
|
|
1984
1940
|
}
|
|
1985
1941
|
return snapshot;
|
|
1986
1942
|
} catch (err) {
|
|
1987
|
-
debug("
|
|
1943
|
+
debug("fetchPromptIdleStatusSnapshot failed", errorMessage(err));
|
|
1988
1944
|
return;
|
|
1989
1945
|
}
|
|
1990
1946
|
}
|
|
@@ -1999,74 +1955,6 @@ function parseSessionStatus(value) {
|
|
|
1999
1955
|
next: typeof status.next === "number" ? status.next : 0
|
|
2000
1956
|
};
|
|
2001
1957
|
}
|
|
2002
|
-
const DEFAULT_DEBOUNCE_MS = 1e3;
|
|
2003
|
-
/**
|
|
2004
|
-
* Encapsulates tab title session-status snapshot fetching with debounced refresh.
|
|
2005
|
-
*
|
|
2006
|
-
* This class replaces the nested `refreshTabTitleSnapshot` /
|
|
2007
|
-
* `scheduleTabTitleSnapshotRefresh` functions that previously lived inside
|
|
2008
|
-
* `createZellijPtyPlugin`. It manages its own timer so the plugin factory
|
|
2009
|
-
* remains a simple composition root.
|
|
2010
|
-
*
|
|
2011
|
-
* Usage:
|
|
2012
|
-
* ```
|
|
2013
|
-
* const refresher = tabTitleManager
|
|
2014
|
-
* ? new TabTitleStatusSnapshotRefresher({ client, workspaceRoot, manager: tabTitleManager })
|
|
2015
|
-
* : undefined
|
|
2016
|
-
*
|
|
2017
|
-
* await refresher?.refreshNow() // initial snapshot
|
|
2018
|
-
* refresher?.scheduleRefresh() // on relevant events
|
|
2019
|
-
* refresher?.dispose() // on shutdown
|
|
2020
|
-
* ```
|
|
2021
|
-
*/
|
|
2022
|
-
var TabTitleStatusSnapshotRefresher = class {
|
|
2023
|
-
client;
|
|
2024
|
-
workspaceRoot;
|
|
2025
|
-
manager;
|
|
2026
|
-
debounceMs;
|
|
2027
|
-
timer;
|
|
2028
|
-
constructor(options) {
|
|
2029
|
-
this.client = options.client;
|
|
2030
|
-
this.workspaceRoot = options.workspaceRoot;
|
|
2031
|
-
this.manager = options.manager;
|
|
2032
|
-
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* Fetches and applies the snapshot immediately, cancelling any pending
|
|
2036
|
-
* debounced refresh.
|
|
2037
|
-
*/
|
|
2038
|
-
async refreshNow() {
|
|
2039
|
-
this.clearTimer();
|
|
2040
|
-
const snapshot = await fetchSessionStatusSnapshot(this.client, this.workspaceRoot);
|
|
2041
|
-
if (snapshot !== void 0) this.manager.applySessionStatusSnapshot(snapshot);
|
|
2042
|
-
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Schedules a debounced snapshot refresh. Subsequent calls while a timer
|
|
2045
|
-
* is pending coalesce into a single refresh.
|
|
2046
|
-
*/
|
|
2047
|
-
scheduleRefresh() {
|
|
2048
|
-
if (this.timer) return;
|
|
2049
|
-
this.timer = setTimeout(() => {
|
|
2050
|
-
this.timer = void 0;
|
|
2051
|
-
this.refreshNow().catch((err) => debug("tab title snapshot refresh failed", errorMessage(err)));
|
|
2052
|
-
}, this.debounceMs);
|
|
2053
|
-
}
|
|
2054
|
-
/**
|
|
2055
|
-
* Clears any pending debounced refresh. Use this during shutdown so a
|
|
2056
|
-
* pending timer does not fire after the manager has been destroyed.
|
|
2057
|
-
*/
|
|
2058
|
-
dispose() {
|
|
2059
|
-
this.clearTimer();
|
|
2060
|
-
}
|
|
2061
|
-
clearTimer() {
|
|
2062
|
-
if (this.timer) {
|
|
2063
|
-
clearTimeout(this.timer);
|
|
2064
|
-
this.timer = void 0;
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
};
|
|
2068
|
-
//#endregion
|
|
2069
|
-
//#region src/zellij/completion-notifications.ts
|
|
2070
1958
|
const completionTitle = "Zellij PTY session completed";
|
|
2071
1959
|
const completionMessage = "A Zellij PTY session completed. Review the finished pane if needed.";
|
|
2072
1960
|
const queuedNoticeHeader = "[OpenCode] Zellij PTY completion notice";
|
|
@@ -2236,7 +2124,7 @@ var SessionCompletionNotificationQueue = class {
|
|
|
2236
2124
|
}
|
|
2237
2125
|
const session = this.context.client.session;
|
|
2238
2126
|
const prompt = session?.prompt ?? session?.promptAsync;
|
|
2239
|
-
const statusSnapshot = await
|
|
2127
|
+
const statusSnapshot = await fetchPromptIdleStatusSnapshot(this.context.client, this.context.workspaceRoot);
|
|
2240
2128
|
const decision = evaluateCompletionPromptDecision({
|
|
2241
2129
|
event: state.event,
|
|
2242
2130
|
config: this.context.config,
|
|
@@ -2338,41 +2226,21 @@ function exitAfterCleanup(signal, code) {
|
|
|
2338
2226
|
//#endregion
|
|
2339
2227
|
//#region src/zellij/tab-title-events.ts
|
|
2340
2228
|
const execFileAsync = promisify(execFile);
|
|
2341
|
-
function isRecord(value) {
|
|
2229
|
+
function isRecord$1(value) {
|
|
2342
2230
|
return typeof value === "object" && value !== null;
|
|
2343
2231
|
}
|
|
2344
|
-
function stringProperty(object, key) {
|
|
2232
|
+
function stringProperty$1(object, key) {
|
|
2345
2233
|
const value = object[key];
|
|
2346
2234
|
return typeof value === "string" ? value : void 0;
|
|
2347
2235
|
}
|
|
2348
|
-
function nestedStringProperty(object, key, nestedKey) {
|
|
2236
|
+
function nestedStringProperty$1(object, key, nestedKey) {
|
|
2349
2237
|
const nested = object[key];
|
|
2350
|
-
if (!isRecord(nested)) return void 0;
|
|
2351
|
-
return stringProperty(nested, nestedKey);
|
|
2352
|
-
}
|
|
2353
|
-
function sessionStatusProperty(object) {
|
|
2354
|
-
const status = object.status;
|
|
2355
|
-
if (!isRecord(status)) return void 0;
|
|
2356
|
-
if (status.type === "idle" || status.type === "busy") return { type: status.type };
|
|
2357
|
-
if (status.type === "retry") return {
|
|
2358
|
-
type: "retry",
|
|
2359
|
-
attempt: typeof status.attempt === "number" ? status.attempt : 0,
|
|
2360
|
-
message: typeof status.message === "string" ? status.message : "",
|
|
2361
|
-
next: typeof status.next === "number" ? status.next : 0
|
|
2362
|
-
};
|
|
2363
|
-
}
|
|
2364
|
-
function inputRequestID(object) {
|
|
2365
|
-
return stringProperty(object, "id") ?? stringProperty(object, "requestID") ?? stringProperty(object, "permissionID");
|
|
2366
|
-
}
|
|
2367
|
-
function inputState(object) {
|
|
2368
|
-
return (stringProperty(object, "status") ?? stringProperty(object, "state") ?? stringProperty(object, "type"))?.toLowerCase();
|
|
2238
|
+
if (!isRecord$1(nested)) return void 0;
|
|
2239
|
+
return stringProperty$1(nested, nestedKey);
|
|
2369
2240
|
}
|
|
2370
|
-
function
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
function deletedSessionID(event) {
|
|
2374
|
-
if (!isRecord(event.properties)) return void 0;
|
|
2375
|
-
return nestedStringProperty(event.properties, "info", "id") ?? stringProperty(event.properties, "sessionID");
|
|
2241
|
+
function deletedSessionID$1(event) {
|
|
2242
|
+
if (!isRecord$1(event.properties)) return void 0;
|
|
2243
|
+
return nestedStringProperty$1(event.properties, "info", "id") ?? stringProperty$1(event.properties, "sessionID");
|
|
2376
2244
|
}
|
|
2377
2245
|
async function readGitBranch(worktree) {
|
|
2378
2246
|
return (await execFileAsync("git", [
|
|
@@ -2397,61 +2265,6 @@ async function getInitialBranch(worktree, readBranch = readGitBranch) {
|
|
|
2397
2265
|
function shouldReadInitialBranch(zellij) {
|
|
2398
2266
|
return Boolean(zellij);
|
|
2399
2267
|
}
|
|
2400
|
-
function handleTabTitleEvent(tabTitleManager, event) {
|
|
2401
|
-
if (event.type === "server.instance.disposed" || event.type === "global.disposed") return tabTitleManager.destroy?.();
|
|
2402
|
-
if (!isRecord(event.properties)) return;
|
|
2403
|
-
const properties = event.properties;
|
|
2404
|
-
switch (event.type) {
|
|
2405
|
-
case "session.status": {
|
|
2406
|
-
const sessionID = stringProperty(properties, "sessionID");
|
|
2407
|
-
const status = sessionStatusProperty(properties);
|
|
2408
|
-
if (sessionID && status && status.type !== "idle") tabTitleManager.updateSessionStatus(sessionID, status);
|
|
2409
|
-
break;
|
|
2410
|
-
}
|
|
2411
|
-
case "session.idle": break;
|
|
2412
|
-
case "session.error": break;
|
|
2413
|
-
case "vcs.branch.updated":
|
|
2414
|
-
tabTitleManager.setBranch(stringProperty(properties, "branch"));
|
|
2415
|
-
break;
|
|
2416
|
-
case "question.asked":
|
|
2417
|
-
case "permission.asked": {
|
|
2418
|
-
const id = inputRequestID(properties);
|
|
2419
|
-
const sessionID = stringProperty(properties, "sessionID");
|
|
2420
|
-
if (id && sessionID) {
|
|
2421
|
-
tabTitleManager.markNeedsInput(id, sessionID);
|
|
2422
|
-
tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
|
|
2423
|
-
}
|
|
2424
|
-
break;
|
|
2425
|
-
}
|
|
2426
|
-
case "permission.updated": {
|
|
2427
|
-
const id = inputRequestID(properties);
|
|
2428
|
-
const sessionID = stringProperty(properties, "sessionID");
|
|
2429
|
-
const state = inputState(properties);
|
|
2430
|
-
if (id && isResolvedInputState(state)) {
|
|
2431
|
-
tabTitleManager.clearNeedsInput(id);
|
|
2432
|
-
if (sessionID) tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
|
|
2433
|
-
} else if (id && sessionID) {
|
|
2434
|
-
tabTitleManager.markNeedsInput(id, sessionID);
|
|
2435
|
-
tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
|
|
2436
|
-
}
|
|
2437
|
-
break;
|
|
2438
|
-
}
|
|
2439
|
-
case "question.replied":
|
|
2440
|
-
case "question.rejected":
|
|
2441
|
-
case "permission.replied": {
|
|
2442
|
-
const id = inputRequestID(properties);
|
|
2443
|
-
const sessionID = stringProperty(properties, "sessionID");
|
|
2444
|
-
if (id) tabTitleManager.clearNeedsInput(id);
|
|
2445
|
-
if (sessionID) tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
|
|
2446
|
-
break;
|
|
2447
|
-
}
|
|
2448
|
-
case "session.deleted": {
|
|
2449
|
-
const sessionID = deletedSessionID(event);
|
|
2450
|
-
if (sessionID) tabTitleManager.removeSession(sessionID);
|
|
2451
|
-
break;
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
2268
|
//#endregion
|
|
2456
2269
|
//#region src/zellij/tab-title.ts
|
|
2457
2270
|
const defaultTabTitleEmojis = {
|
|
@@ -2470,10 +2283,237 @@ function sanitizeTitle(title, maxLength = 90) {
|
|
|
2470
2283
|
if (chars.length > maxLength) cleaned = `${chars.slice(0, maxLength - 1).join("")}…`;
|
|
2471
2284
|
return cleaned;
|
|
2472
2285
|
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2286
|
+
function isRecord(value) {
|
|
2287
|
+
return typeof value === "object" && value !== null;
|
|
2288
|
+
}
|
|
2289
|
+
function stringProperty(object, key) {
|
|
2290
|
+
const value = object[key];
|
|
2291
|
+
return typeof value === "string" ? value : void 0;
|
|
2292
|
+
}
|
|
2293
|
+
function nestedStringProperty(object, key, nestedKey) {
|
|
2294
|
+
const nested = object[key];
|
|
2295
|
+
if (!isRecord(nested)) return void 0;
|
|
2296
|
+
return stringProperty(nested, nestedKey);
|
|
2297
|
+
}
|
|
2298
|
+
function sessionStatusType(properties) {
|
|
2299
|
+
const status = properties.status;
|
|
2300
|
+
if (!isRecord(status)) return void 0;
|
|
2301
|
+
const type = status.type;
|
|
2302
|
+
if (type === "idle" || type === "busy") return type;
|
|
2303
|
+
if (type === "retry") return "retry";
|
|
2304
|
+
}
|
|
2305
|
+
function inputRequestID(properties) {
|
|
2306
|
+
return stringProperty(properties, "id") ?? stringProperty(properties, "requestID") ?? stringProperty(properties, "permissionID");
|
|
2307
|
+
}
|
|
2308
|
+
function inputState(properties) {
|
|
2309
|
+
return (stringProperty(properties, "status") ?? stringProperty(properties, "state") ?? stringProperty(properties, "type"))?.toLowerCase();
|
|
2310
|
+
}
|
|
2311
|
+
function isResolvedInputState(state) {
|
|
2312
|
+
return state === "approved" || state === "denied" || state === "rejected" || state === "resolved" || state === "replied";
|
|
2313
|
+
}
|
|
2314
|
+
function deletedSessionID(properties) {
|
|
2315
|
+
return nestedStringProperty(properties, "info", "id") ?? stringProperty(properties, "sessionID");
|
|
2316
|
+
}
|
|
2317
|
+
var TabTitleIdentityModel = class {
|
|
2318
|
+
ready;
|
|
2319
|
+
projectName;
|
|
2476
2320
|
branchName;
|
|
2321
|
+
worktree;
|
|
2322
|
+
readBranch;
|
|
2323
|
+
refreshGeneration = 0;
|
|
2324
|
+
constructor(options) {
|
|
2325
|
+
this.projectName = options.projectName;
|
|
2326
|
+
this.worktree = options.worktree;
|
|
2327
|
+
this.readBranch = options.readBranch;
|
|
2328
|
+
this.ready = this.refreshBranch("initial");
|
|
2329
|
+
}
|
|
2330
|
+
async refreshBranch(_reason) {
|
|
2331
|
+
const generation = ++this.refreshGeneration;
|
|
2332
|
+
try {
|
|
2333
|
+
const result = await this.readBranch(this.worktree);
|
|
2334
|
+
if (generation !== this.refreshGeneration) return;
|
|
2335
|
+
const trimmed = result.trim() || void 0;
|
|
2336
|
+
this.branchName = trimmed;
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
if (generation !== this.refreshGeneration) return;
|
|
2339
|
+
debug("refreshBranch failed", errorMessage(error));
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
handleEvent(event) {
|
|
2343
|
+
if (event.type === "vcs.branch.updated") return this.refreshBranch("vcs.branch.updated");
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
var TabTitleActivityModel = class {
|
|
2347
|
+
status = "idle";
|
|
2348
|
+
worktreeDirectory;
|
|
2349
|
+
sessions = /* @__PURE__ */ new Map();
|
|
2350
|
+
scopedSessions = /* @__PURE__ */ new Set();
|
|
2351
|
+
runningSessions = /* @__PURE__ */ new Set();
|
|
2352
|
+
pendingInputs = /* @__PURE__ */ new Map();
|
|
2353
|
+
constructor(options) {
|
|
2354
|
+
this.worktreeDirectory = options.worktreeDirectory;
|
|
2355
|
+
}
|
|
2356
|
+
getSession(sessionID) {
|
|
2357
|
+
return this.scopedSessions.has(sessionID) ? this.sessions.get(sessionID) : void 0;
|
|
2358
|
+
}
|
|
2359
|
+
hasPendingInput(sessionID, requestID) {
|
|
2360
|
+
return this.pendingInputs.has(`${sessionID}:${requestID}`);
|
|
2361
|
+
}
|
|
2362
|
+
handleEvent(event) {
|
|
2363
|
+
if (!isRecord(event.properties)) return;
|
|
2364
|
+
const properties = event.properties;
|
|
2365
|
+
switch (event.type) {
|
|
2366
|
+
case "session.created":
|
|
2367
|
+
case "session.updated": {
|
|
2368
|
+
const info = properties.info;
|
|
2369
|
+
if (isRecord(info)) {
|
|
2370
|
+
const id = stringProperty(info, "id");
|
|
2371
|
+
if (id) this.storeSession(id, info);
|
|
2372
|
+
}
|
|
2373
|
+
break;
|
|
2374
|
+
}
|
|
2375
|
+
case "session.status": {
|
|
2376
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2377
|
+
const statusType = sessionStatusType(properties);
|
|
2378
|
+
if (sessionID && statusType) {
|
|
2379
|
+
if (statusType === "idle") {
|
|
2380
|
+
if (this.runningSessions.has(sessionID)) {
|
|
2381
|
+
this.runningSessions.delete(sessionID);
|
|
2382
|
+
this.updateStatus();
|
|
2383
|
+
}
|
|
2384
|
+
} else if (statusType === "busy" || statusType === "retry") {
|
|
2385
|
+
if (this.scopedSessions.has(sessionID)) {
|
|
2386
|
+
this.runningSessions.add(sessionID);
|
|
2387
|
+
this.updateStatus();
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
break;
|
|
2392
|
+
}
|
|
2393
|
+
case "session.idle":
|
|
2394
|
+
case "session.error": {
|
|
2395
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2396
|
+
if (sessionID && this.runningSessions.has(sessionID)) {
|
|
2397
|
+
this.runningSessions.delete(sessionID);
|
|
2398
|
+
this.updateStatus();
|
|
2399
|
+
}
|
|
2400
|
+
break;
|
|
2401
|
+
}
|
|
2402
|
+
case "question.asked":
|
|
2403
|
+
case "permission.asked": {
|
|
2404
|
+
const id = inputRequestID(properties);
|
|
2405
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2406
|
+
if (id && sessionID && this.scopedSessions.has(sessionID)) {
|
|
2407
|
+
this.pendingInputs.set(`${sessionID}:${id}`, sessionID);
|
|
2408
|
+
this.runningSessions.add(sessionID);
|
|
2409
|
+
this.updateStatus();
|
|
2410
|
+
}
|
|
2411
|
+
break;
|
|
2412
|
+
}
|
|
2413
|
+
case "permission.updated": {
|
|
2414
|
+
const id = inputRequestID(properties);
|
|
2415
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2416
|
+
const state = inputState(properties);
|
|
2417
|
+
if (id && isResolvedInputState(state)) {
|
|
2418
|
+
this.pendingInputs.delete(`${sessionID}:${id}`);
|
|
2419
|
+
if (sessionID && this.runningSessions.has(sessionID)) this.runningSessions.add(sessionID);
|
|
2420
|
+
this.updateStatus();
|
|
2421
|
+
} else if (id && sessionID && this.scopedSessions.has(sessionID)) {
|
|
2422
|
+
this.pendingInputs.set(`${sessionID}:${id}`, sessionID);
|
|
2423
|
+
this.runningSessions.add(sessionID);
|
|
2424
|
+
this.updateStatus();
|
|
2425
|
+
}
|
|
2426
|
+
break;
|
|
2427
|
+
}
|
|
2428
|
+
case "question.replied":
|
|
2429
|
+
case "question.rejected":
|
|
2430
|
+
case "permission.replied": {
|
|
2431
|
+
const id = inputRequestID(properties);
|
|
2432
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2433
|
+
if (id) this.pendingInputs.delete(`${sessionID}:${id}`);
|
|
2434
|
+
if (sessionID && this.runningSessions.has(sessionID)) this.runningSessions.add(sessionID);
|
|
2435
|
+
this.updateStatus();
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
case "session.deleted": {
|
|
2439
|
+
const sessionID = deletedSessionID(properties);
|
|
2440
|
+
if (sessionID) {
|
|
2441
|
+
this.removeSessionAndDescendants(sessionID);
|
|
2442
|
+
this.updateStatus();
|
|
2443
|
+
}
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
storeSession(id, info) {
|
|
2449
|
+
const directory = stringProperty(info, "directory");
|
|
2450
|
+
const parentID = stringProperty(info, "parentID");
|
|
2451
|
+
this.sessions.set(id, {
|
|
2452
|
+
directory,
|
|
2453
|
+
parentID
|
|
2454
|
+
});
|
|
2455
|
+
const isDirectlyScoped = directory === this.worktreeDirectory;
|
|
2456
|
+
const isDescendantScoped = parentID ? this.scopedSessions.has(parentID) : false;
|
|
2457
|
+
if (this.scopedSessions.has(id) || isDirectlyScoped || isDescendantScoped) this.scopedSessions.add(id);
|
|
2458
|
+
}
|
|
2459
|
+
removeSessionAndDescendants(rootID) {
|
|
2460
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
2461
|
+
toRemove.add(rootID);
|
|
2462
|
+
let changed = true;
|
|
2463
|
+
while (changed) {
|
|
2464
|
+
changed = false;
|
|
2465
|
+
for (const [id, session] of this.sessions) if (!toRemove.has(id) && session.parentID && toRemove.has(session.parentID)) {
|
|
2466
|
+
toRemove.add(id);
|
|
2467
|
+
changed = true;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
for (const id of toRemove) {
|
|
2471
|
+
this.sessions.delete(id);
|
|
2472
|
+
this.scopedSessions.delete(id);
|
|
2473
|
+
this.runningSessions.delete(id);
|
|
2474
|
+
}
|
|
2475
|
+
for (const [key, sessionID] of [...this.pendingInputs.entries()]) if (toRemove.has(sessionID)) this.pendingInputs.delete(key);
|
|
2476
|
+
}
|
|
2477
|
+
updateStatus() {
|
|
2478
|
+
if (this.pendingInputs.size > 0) this.status = "needs-input";
|
|
2479
|
+
else if (this.runningSessions.size > 0) this.status = "running";
|
|
2480
|
+
else this.status = "idle";
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
var TabTitleActor = class {
|
|
2484
|
+
ready;
|
|
2485
|
+
identity;
|
|
2486
|
+
activity;
|
|
2487
|
+
emojis;
|
|
2488
|
+
constructor(options) {
|
|
2489
|
+
this.identity = options.identity;
|
|
2490
|
+
this.activity = options.activity;
|
|
2491
|
+
this.emojis = {
|
|
2492
|
+
...defaultTabTitleEmojis,
|
|
2493
|
+
...options.emojis
|
|
2494
|
+
};
|
|
2495
|
+
this.ready = this.identity.ready;
|
|
2496
|
+
}
|
|
2497
|
+
get context() {
|
|
2498
|
+
return {
|
|
2499
|
+
projectName: this.identity.projectName,
|
|
2500
|
+
branchName: this.identity.branchName,
|
|
2501
|
+
status: this.activity.status
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
get title() {
|
|
2505
|
+
return formatTabTitle({
|
|
2506
|
+
...this.context,
|
|
2507
|
+
emojis: this.emojis
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
async handleEvent(event) {
|
|
2511
|
+
this.activity.handleEvent(event);
|
|
2512
|
+
const identityResult = this.identity.handleEvent(event);
|
|
2513
|
+
if (identityResult instanceof Promise) await identityResult;
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
var TabTitleManager = class {
|
|
2477
2517
|
desiredTitle;
|
|
2478
2518
|
lastSyncedTitle;
|
|
2479
2519
|
debounceTimer;
|
|
@@ -2484,7 +2524,6 @@ var TabTitleManager = class {
|
|
|
2484
2524
|
debounceMs;
|
|
2485
2525
|
retryInitialMs;
|
|
2486
2526
|
retryMaxMs;
|
|
2487
|
-
projectName;
|
|
2488
2527
|
cli;
|
|
2489
2528
|
emojis;
|
|
2490
2529
|
enabled;
|
|
@@ -2493,9 +2532,8 @@ var TabTitleManager = class {
|
|
|
2493
2532
|
originalTabTitleLoaded = false;
|
|
2494
2533
|
originalTabTitlePromise;
|
|
2495
2534
|
destroyPromise;
|
|
2535
|
+
actor;
|
|
2496
2536
|
constructor(options) {
|
|
2497
|
-
this.projectName = options.projectName;
|
|
2498
|
-
this.branchName = options.branchName?.trim() || void 0;
|
|
2499
2537
|
this.cli = options.cli ?? new ZellijCli();
|
|
2500
2538
|
this.emojis = {
|
|
2501
2539
|
...defaultTabTitleEmojis,
|
|
@@ -2504,80 +2542,12 @@ var TabTitleManager = class {
|
|
|
2504
2542
|
this.debounceMs = options.debounceMs ?? 300;
|
|
2505
2543
|
this.retryInitialMs = options.retryInitialMs ?? 250;
|
|
2506
2544
|
this.retryMaxMs = options.retryMaxMs ?? 5e3;
|
|
2507
|
-
this.enabled = Boolean(process.env.ZELLIJ);
|
|
2508
|
-
|
|
2509
|
-
setBranch(branch) {
|
|
2510
|
-
const trimmed = branch?.trim() || void 0;
|
|
2511
|
-
if (this.branchName === trimmed) return;
|
|
2512
|
-
this.branchName = trimmed;
|
|
2513
|
-
this.scheduleUpdate();
|
|
2514
|
-
}
|
|
2515
|
-
/**
|
|
2516
|
-
* Applies a snapshot of session statuses from the server.
|
|
2517
|
-
*
|
|
2518
|
-
* This replaces the entire base status map. Sessions absent from the snapshot
|
|
2519
|
-
* (e.g. because they ended) are removed so stale busy/idle entries do not
|
|
2520
|
-
* persist. This is the authoritative source for the "running vs idle" base
|
|
2521
|
-
* state; individual session.status events still perform optimistic updates
|
|
2522
|
-
* for immediacy but the snapshot corrects drift.
|
|
2523
|
-
*
|
|
2524
|
-
* The `needs-input` overlay remains independent — it is managed purely by
|
|
2525
|
-
* events (question/permission asked/replied/etc.) and always takes priority
|
|
2526
|
-
* over the snapshot base when computing the displayed title.
|
|
2527
|
-
*/
|
|
2528
|
-
applySessionStatusSnapshot(statuses) {
|
|
2529
|
-
for (const sessionID of this.sessionStatuses.keys()) if (!(sessionID in statuses)) this.sessionStatuses.delete(sessionID);
|
|
2530
|
-
for (const [sessionID, status] of Object.entries(statuses)) {
|
|
2531
|
-
const activity = status.type === "idle" ? "idle" : "running";
|
|
2532
|
-
if (this.sessionStatuses.get(sessionID) !== activity) this.sessionStatuses.set(sessionID, activity);
|
|
2533
|
-
}
|
|
2534
|
-
this.scheduleUpdate();
|
|
2535
|
-
}
|
|
2536
|
-
updateSessionStatus(sessionID, status) {
|
|
2537
|
-
const activity = status.type === "idle" ? "idle" : "running";
|
|
2538
|
-
if (this.sessionStatuses.get(sessionID) === activity) return;
|
|
2539
|
-
this.sessionStatuses.set(sessionID, activity);
|
|
2540
|
-
this.scheduleUpdate();
|
|
2541
|
-
}
|
|
2542
|
-
markSessionIdle(sessionID) {
|
|
2543
|
-
this.updateSessionStatus(sessionID, { type: "idle" });
|
|
2544
|
-
}
|
|
2545
|
-
removeSession(sessionID) {
|
|
2546
|
-
const hadSessionStatus = this.sessionStatuses.delete(sessionID);
|
|
2547
|
-
let hadPendingInput = false;
|
|
2548
|
-
for (const [id, pendingSessionID] of this.pendingInputs) if (pendingSessionID === sessionID) {
|
|
2549
|
-
this.pendingInputs.delete(id);
|
|
2550
|
-
hadPendingInput = true;
|
|
2551
|
-
}
|
|
2552
|
-
if (!hadSessionStatus && !hadPendingInput) return;
|
|
2553
|
-
this.scheduleUpdate();
|
|
2554
|
-
}
|
|
2555
|
-
markNeedsInput(id, sessionID) {
|
|
2556
|
-
if (this.pendingInputs.get(id) === sessionID) return;
|
|
2557
|
-
this.pendingInputs.set(id, sessionID);
|
|
2558
|
-
this.scheduleUpdate();
|
|
2559
|
-
}
|
|
2560
|
-
clearNeedsInput(id) {
|
|
2561
|
-
if (!this.pendingInputs.delete(id)) return;
|
|
2562
|
-
this.scheduleUpdate();
|
|
2563
|
-
}
|
|
2564
|
-
get isBusy() {
|
|
2565
|
-
for (const activity of this.sessionStatuses.values()) if (activity === "running") return true;
|
|
2566
|
-
return false;
|
|
2567
|
-
}
|
|
2568
|
-
get needsInput() {
|
|
2569
|
-
return this.pendingInputs.size > 0;
|
|
2570
|
-
}
|
|
2571
|
-
get status() {
|
|
2572
|
-
if (this.needsInput) return "needs-input";
|
|
2573
|
-
if (this.isBusy) return "running";
|
|
2574
|
-
return "idle";
|
|
2545
|
+
this.enabled = Boolean(process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME);
|
|
2546
|
+
this.actor = options.actor;
|
|
2575
2547
|
}
|
|
2576
2548
|
buildTitle() {
|
|
2577
2549
|
return sanitizeTitle(formatTabTitle({
|
|
2578
|
-
|
|
2579
|
-
branchName: this.branchName,
|
|
2580
|
-
status: this.status,
|
|
2550
|
+
...this.actor.context,
|
|
2581
2551
|
emojis: this.emojis
|
|
2582
2552
|
}));
|
|
2583
2553
|
}
|
|
@@ -2753,10 +2723,18 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2753
2723
|
registerShutdownCleanup();
|
|
2754
2724
|
const workspaceRoot = getWorkspaceRoot(input);
|
|
2755
2725
|
const projectName = getProjectName(workspaceRoot);
|
|
2756
|
-
const
|
|
2757
|
-
const tabTitleManager = config.tabTitle.enabled ? new TabTitleManager({
|
|
2726
|
+
const identityModel = config.tabTitle.enabled ? new TabTitleIdentityModel({
|
|
2758
2727
|
projectName,
|
|
2759
|
-
|
|
2728
|
+
worktree: workspaceRoot,
|
|
2729
|
+
readBranch: async (worktree) => shouldReadInitialBranch(process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) ? await getInitialBranch(worktree) ?? "" : ""
|
|
2730
|
+
}) : void 0;
|
|
2731
|
+
const activityModel = config.tabTitle.enabled ? new TabTitleActivityModel({ worktreeDirectory: workspaceRoot }) : void 0;
|
|
2732
|
+
const actor = identityModel && activityModel ? new TabTitleActor({
|
|
2733
|
+
identity: identityModel,
|
|
2734
|
+
activity: activityModel
|
|
2735
|
+
}) : void 0;
|
|
2736
|
+
const tabTitleManager = config.tabTitle.enabled && actor ? new TabTitleManager({
|
|
2737
|
+
actor,
|
|
2760
2738
|
debounceMs: config.tabTitle.debounceMs,
|
|
2761
2739
|
emojis: {
|
|
2762
2740
|
idle: config.tabTitle.emojiIdle,
|
|
@@ -2790,22 +2768,16 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2790
2768
|
}
|
|
2791
2769
|
});
|
|
2792
2770
|
subscriberManager.setLifecycleHooks(completionNotifications ? { onSessionTerminal: (event) => void completionNotifications.handleSessionTerminal(event).catch((error) => debug("completion notification lifecycle hook failed", errorMessage(error))) } : void 0);
|
|
2793
|
-
|
|
2794
|
-
client,
|
|
2795
|
-
workspaceRoot,
|
|
2796
|
-
manager: tabTitleManager,
|
|
2797
|
-
debounceMs: 1e3
|
|
2798
|
-
}) : void 0;
|
|
2799
|
-
tabTitleSnapshotRefresher?.refreshNow().catch((error) => debug("initial tab title snapshot refresh failed", errorMessage(error)));
|
|
2771
|
+
if (actor) await actor.ready;
|
|
2800
2772
|
tabTitleManager?.renderImmediate().catch((error) => debug("initial tab title render failed", errorMessage(error)));
|
|
2801
2773
|
if (config.autoUpdate) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
|
|
2802
2774
|
return {
|
|
2803
2775
|
async event(input) {
|
|
2804
2776
|
const event = input.event;
|
|
2805
|
-
if (tabTitleManager) {
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2777
|
+
if (actor && tabTitleManager) {
|
|
2778
|
+
await actor.handleEvent(event);
|
|
2779
|
+
if (event.type === "server.instance.disposed" || event.type === "global.disposed") await tabTitleManager.destroy();
|
|
2780
|
+
else tabTitleManager.scheduleUpdate();
|
|
2809
2781
|
}
|
|
2810
2782
|
if (event.type === "server.instance.disposed" || event.type === "global.disposed") {
|
|
2811
2783
|
completionNotifications?.clearAll();
|
|
@@ -2813,7 +2785,7 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2813
2785
|
subscriberManager.setLifecycleHooks(void 0);
|
|
2814
2786
|
}
|
|
2815
2787
|
if (event.type === "session.deleted") {
|
|
2816
|
-
const sessionID = deletedSessionID(event);
|
|
2788
|
+
const sessionID = deletedSessionID$1(event);
|
|
2817
2789
|
if (!sessionID) return;
|
|
2818
2790
|
const sessions = sessionManager.listByOpenCodeSession(sessionID);
|
|
2819
2791
|
for (const session of sessions) completionNotifications?.clearSession(session.id);
|