opencode-zellij 0.0.12 → 0.0.13
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 +37 -166
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -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,
|
|
@@ -2405,11 +2293,20 @@ function handleTabTitleEvent(tabTitleManager, event) {
|
|
|
2405
2293
|
case "session.status": {
|
|
2406
2294
|
const sessionID = stringProperty(properties, "sessionID");
|
|
2407
2295
|
const status = sessionStatusProperty(properties);
|
|
2408
|
-
if (sessionID && status
|
|
2296
|
+
if (sessionID && status) if (status.type === "idle") tabTitleManager.markSessionIdle(sessionID);
|
|
2297
|
+
else tabTitleManager.updateSessionStatus(sessionID, status);
|
|
2298
|
+
break;
|
|
2299
|
+
}
|
|
2300
|
+
case "session.idle": {
|
|
2301
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2302
|
+
if (sessionID) tabTitleManager.markSessionIdle(sessionID);
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
2305
|
+
case "session.error": {
|
|
2306
|
+
const sessionID = stringProperty(properties, "sessionID");
|
|
2307
|
+
if (sessionID) tabTitleManager.markSessionIdle(sessionID);
|
|
2409
2308
|
break;
|
|
2410
2309
|
}
|
|
2411
|
-
case "session.idle": break;
|
|
2412
|
-
case "session.error": break;
|
|
2413
2310
|
case "vcs.branch.updated":
|
|
2414
2311
|
tabTitleManager.setBranch(stringProperty(properties, "branch"));
|
|
2415
2312
|
break;
|
|
@@ -2471,7 +2368,7 @@ function sanitizeTitle(title, maxLength = 90) {
|
|
|
2471
2368
|
return cleaned;
|
|
2472
2369
|
}
|
|
2473
2370
|
var TabTitleManager = class {
|
|
2474
|
-
|
|
2371
|
+
runningSessions = /* @__PURE__ */ new Set();
|
|
2475
2372
|
pendingInputs = /* @__PURE__ */ new Map();
|
|
2476
2373
|
branchName;
|
|
2477
2374
|
desiredTitle;
|
|
@@ -2512,44 +2409,30 @@ var TabTitleManager = class {
|
|
|
2512
2409
|
this.branchName = trimmed;
|
|
2513
2410
|
this.scheduleUpdate();
|
|
2514
2411
|
}
|
|
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
2412
|
updateSessionStatus(sessionID, status) {
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2413
|
+
const isRunning = status.type === "busy" || status.type === "retry";
|
|
2414
|
+
const wasRunning = this.runningSessions.has(sessionID);
|
|
2415
|
+
if (isRunning) {
|
|
2416
|
+
if (!wasRunning) {
|
|
2417
|
+
this.runningSessions.add(sessionID);
|
|
2418
|
+
this.scheduleUpdate();
|
|
2419
|
+
}
|
|
2420
|
+
} else if (wasRunning) {
|
|
2421
|
+
this.runningSessions.delete(sessionID);
|
|
2422
|
+
this.scheduleUpdate();
|
|
2423
|
+
}
|
|
2541
2424
|
}
|
|
2542
2425
|
markSessionIdle(sessionID) {
|
|
2543
|
-
this.
|
|
2426
|
+
if (this.runningSessions.delete(sessionID)) this.scheduleUpdate();
|
|
2544
2427
|
}
|
|
2545
2428
|
removeSession(sessionID) {
|
|
2546
|
-
const
|
|
2429
|
+
const hadRunning = this.runningSessions.delete(sessionID);
|
|
2547
2430
|
let hadPendingInput = false;
|
|
2548
2431
|
for (const [id, pendingSessionID] of this.pendingInputs) if (pendingSessionID === sessionID) {
|
|
2549
2432
|
this.pendingInputs.delete(id);
|
|
2550
2433
|
hadPendingInput = true;
|
|
2551
2434
|
}
|
|
2552
|
-
if (!
|
|
2435
|
+
if (!hadRunning && !hadPendingInput) return;
|
|
2553
2436
|
this.scheduleUpdate();
|
|
2554
2437
|
}
|
|
2555
2438
|
markNeedsInput(id, sessionID) {
|
|
@@ -2562,8 +2445,7 @@ var TabTitleManager = class {
|
|
|
2562
2445
|
this.scheduleUpdate();
|
|
2563
2446
|
}
|
|
2564
2447
|
get isBusy() {
|
|
2565
|
-
|
|
2566
|
-
return false;
|
|
2448
|
+
return this.runningSessions.size > 0;
|
|
2567
2449
|
}
|
|
2568
2450
|
get needsInput() {
|
|
2569
2451
|
return this.pendingInputs.size > 0;
|
|
@@ -2790,23 +2672,12 @@ function createZellijPtyPlugin(dependencies = {}) {
|
|
|
2790
2672
|
}
|
|
2791
2673
|
});
|
|
2792
2674
|
subscriberManager.setLifecycleHooks(completionNotifications ? { onSessionTerminal: (event) => void completionNotifications.handleSessionTerminal(event).catch((error) => debug("completion notification lifecycle hook failed", errorMessage(error))) } : void 0);
|
|
2793
|
-
const tabTitleSnapshotRefresher = tabTitleManager ? new TabTitleStatusSnapshotRefresher({
|
|
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)));
|
|
2800
2675
|
tabTitleManager?.renderImmediate().catch((error) => debug("initial tab title render failed", errorMessage(error)));
|
|
2801
2676
|
if (config.autoUpdate) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
|
|
2802
2677
|
return {
|
|
2803
2678
|
async event(input) {
|
|
2804
2679
|
const event = input.event;
|
|
2805
|
-
if (tabTitleManager)
|
|
2806
|
-
if (event.type === "server.instance.disposed" || event.type === "global.disposed") tabTitleSnapshotRefresher?.dispose();
|
|
2807
|
-
await handleTabTitleEvent(tabTitleManager, event);
|
|
2808
|
-
if (shouldRefreshTabTitleStatusSnapshot(event)) tabTitleSnapshotRefresher?.scheduleRefresh();
|
|
2809
|
-
}
|
|
2680
|
+
if (tabTitleManager) await handleTabTitleEvent(tabTitleManager, event);
|
|
2810
2681
|
if (event.type === "server.instance.disposed" || event.type === "global.disposed") {
|
|
2811
2682
|
completionNotifications?.clearAll();
|
|
2812
2683
|
completionNotifications?.dispose();
|