opencode-zellij 0.0.13 → 0.0.15

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 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$1(pkg)) return {
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$1(pkg) && pkg.name === "opencode-zellij" && typeof pkg.version === "string" && pkg.version.length > 0) {
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$1(value) {
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$1(data) && typeof data.latest === "string") return data.latest;
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$1(object, keys) {
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$1(object, ["name", "title"]);
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) {
@@ -1099,6 +1099,7 @@ var SubscriberManager = class {
1099
1099
  startingSessions = /* @__PURE__ */ new Map();
1100
1100
  spawnProcess;
1101
1101
  dumpScreen;
1102
+ paneExists;
1102
1103
  closePane;
1103
1104
  lifecycleHooks;
1104
1105
  terminalTailLines;
@@ -1107,6 +1108,7 @@ var SubscriberManager = class {
1107
1108
  this.maxBufferLines = maxBufferLines;
1108
1109
  this.spawnProcess = dependencies.spawn ?? spawn;
1109
1110
  this.dumpScreen = dependencies.dumpScreen ?? zellijCli.dumpScreen;
1111
+ this.paneExists = dependencies.paneExists ?? zellijCli.paneExists;
1110
1112
  this.closePane = dependencies.closePane ?? zellijCli.closePane;
1111
1113
  this.lifecycleHooks = dependencies.lifecycleHooks;
1112
1114
  this.terminalTailLines = dependencies.terminalTailLines ?? 200;
@@ -1276,8 +1278,7 @@ var SubscriberManager = class {
1276
1278
  handleStderr(sessionId, child, chunk) {
1277
1279
  const state = this.subscribers.get(sessionId);
1278
1280
  if (!state || state.child !== child) return;
1279
- state.stderr.push(...splitLines(chunk));
1280
- if (state.stderr.length > maxStderrLines) state.stderr = state.stderr.slice(state.stderr.length - maxStderrLines);
1281
+ this.appendStderr(state, ...splitLines(chunk));
1281
1282
  }
1282
1283
  handleSubscriberExit(sessionId, child) {
1283
1284
  const state = this.subscribers.get(sessionId);
@@ -1285,18 +1286,46 @@ var SubscriberManager = class {
1285
1286
  if (state.child !== child) return;
1286
1287
  state.child = null;
1287
1288
  state.lastExitedAt = (/* @__PURE__ */ new Date()).toISOString();
1288
- if (this.sessions.find(sessionId)?.status !== "terminal") state.stderr.push(`[zellij-pty] subscriber exited at ${state.lastExitedAt}; last buffered output is retained.`);
1289
- if (state.stderr.length > maxStderrLines) state.stderr = state.stderr.slice(state.stderr.length - maxStderrLines);
1289
+ if (this.sessions.find(sessionId)?.status !== "terminal") this.appendStderr(state, `[zellij-pty] subscriber exited at ${state.lastExitedAt}; last buffered output is retained.`);
1290
+ this.reconcileSubscriberTermination(sessionId, state, "subscriber_exit");
1290
1291
  }
1291
1292
  handleSubscriberError(sessionId, child, error) {
1292
1293
  const state = this.subscribers.get(sessionId);
1293
1294
  if (state?.child === child) {
1294
- state.stderr.push(error.message);
1295
+ this.appendStderr(state, error.message);
1295
1296
  state.child = null;
1296
1297
  state.lastExitedAt = (/* @__PURE__ */ new Date()).toISOString();
1297
- this.sessions.updateStatus(sessionId, "unknown");
1298
+ if (this.sessions.find(sessionId)?.status !== "terminal") this.sessions.updateStatus(sessionId, "unknown");
1299
+ this.reconcileSubscriberTermination(sessionId, state, "subscriber_error");
1298
1300
  }
1299
1301
  }
1302
+ appendStderr(state, ...lines) {
1303
+ state.stderr.push(...lines);
1304
+ if (state.stderr.length > maxStderrLines) state.stderr = state.stderr.slice(state.stderr.length - maxStderrLines);
1305
+ }
1306
+ async reconcileSubscriberTermination(sessionId, state, reason) {
1307
+ const session = this.sessions.find(sessionId);
1308
+ if (!session || session.status === "terminal" || this.subscribers.get(sessionId) !== state || state.child) return;
1309
+ let paneExists;
1310
+ try {
1311
+ paneExists = await this.paneExists(session.paneId);
1312
+ } catch (error) {
1313
+ const latestState = this.subscribers.get(sessionId);
1314
+ const latestSession = this.sessions.find(sessionId);
1315
+ if (!latestState || latestState !== state || latestState.child || !latestSession || latestSession.status === "terminal") return;
1316
+ this.appendStderr(latestState, `[zellij-pty] ${reason} reconciliation could not verify pane ${latestSession.paneId}: ${errorMessage(error)}`);
1317
+ return;
1318
+ }
1319
+ const latestState = this.subscribers.get(sessionId);
1320
+ const latestSession = this.sessions.find(sessionId);
1321
+ if (!latestState || latestState !== state || latestState.child || !latestSession || latestSession.status === "terminal") return;
1322
+ if (paneExists === false) {
1323
+ this.markSessionTerminal(sessionId, reason);
1324
+ unregisterPaneFromWatchdog(sessionId);
1325
+ return;
1326
+ }
1327
+ if (paneExists === void 0) this.appendStderr(latestState, `[zellij-pty] ${reason} reconciliation could not confirm whether pane ${latestSession.paneId} still exists; leaving session non-terminal.`);
1328
+ }
1300
1329
  markSessionTerminal(sessionId, reason, input = {}) {
1301
1330
  const state = this.subscribers.get(sessionId);
1302
1331
  if (!state) return;
@@ -2226,41 +2255,21 @@ function exitAfterCleanup(signal, code) {
2226
2255
  //#endregion
2227
2256
  //#region src/zellij/tab-title-events.ts
2228
2257
  const execFileAsync = promisify(execFile);
2229
- function isRecord(value) {
2258
+ function isRecord$1(value) {
2230
2259
  return typeof value === "object" && value !== null;
2231
2260
  }
2232
- function stringProperty(object, key) {
2261
+ function stringProperty$1(object, key) {
2233
2262
  const value = object[key];
2234
2263
  return typeof value === "string" ? value : void 0;
2235
2264
  }
2236
- function nestedStringProperty(object, key, nestedKey) {
2265
+ function nestedStringProperty$1(object, key, nestedKey) {
2237
2266
  const nested = object[key];
2238
- if (!isRecord(nested)) return void 0;
2239
- return stringProperty(nested, nestedKey);
2267
+ if (!isRecord$1(nested)) return void 0;
2268
+ return stringProperty$1(nested, nestedKey);
2240
2269
  }
2241
- function sessionStatusProperty(object) {
2242
- const status = object.status;
2243
- if (!isRecord(status)) return void 0;
2244
- if (status.type === "idle" || status.type === "busy") return { type: status.type };
2245
- if (status.type === "retry") return {
2246
- type: "retry",
2247
- attempt: typeof status.attempt === "number" ? status.attempt : 0,
2248
- message: typeof status.message === "string" ? status.message : "",
2249
- next: typeof status.next === "number" ? status.next : 0
2250
- };
2251
- }
2252
- function inputRequestID(object) {
2253
- return stringProperty(object, "id") ?? stringProperty(object, "requestID") ?? stringProperty(object, "permissionID");
2254
- }
2255
- function inputState(object) {
2256
- return (stringProperty(object, "status") ?? stringProperty(object, "state") ?? stringProperty(object, "type"))?.toLowerCase();
2257
- }
2258
- function isResolvedInputState(state) {
2259
- return state === "approved" || state === "denied" || state === "rejected" || state === "resolved" || state === "replied";
2260
- }
2261
- function deletedSessionID(event) {
2262
- if (!isRecord(event.properties)) return void 0;
2263
- return nestedStringProperty(event.properties, "info", "id") ?? stringProperty(event.properties, "sessionID");
2270
+ function deletedSessionID$1(event) {
2271
+ if (!isRecord$1(event.properties)) return void 0;
2272
+ return nestedStringProperty$1(event.properties, "info", "id") ?? stringProperty$1(event.properties, "sessionID");
2264
2273
  }
2265
2274
  async function readGitBranch(worktree) {
2266
2275
  return (await execFileAsync("git", [
@@ -2285,70 +2294,6 @@ async function getInitialBranch(worktree, readBranch = readGitBranch) {
2285
2294
  function shouldReadInitialBranch(zellij) {
2286
2295
  return Boolean(zellij);
2287
2296
  }
2288
- function handleTabTitleEvent(tabTitleManager, event) {
2289
- if (event.type === "server.instance.disposed" || event.type === "global.disposed") return tabTitleManager.destroy?.();
2290
- if (!isRecord(event.properties)) return;
2291
- const properties = event.properties;
2292
- switch (event.type) {
2293
- case "session.status": {
2294
- const sessionID = stringProperty(properties, "sessionID");
2295
- const status = sessionStatusProperty(properties);
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);
2308
- break;
2309
- }
2310
- case "vcs.branch.updated":
2311
- tabTitleManager.setBranch(stringProperty(properties, "branch"));
2312
- break;
2313
- case "question.asked":
2314
- case "permission.asked": {
2315
- const id = inputRequestID(properties);
2316
- const sessionID = stringProperty(properties, "sessionID");
2317
- if (id && sessionID) {
2318
- tabTitleManager.markNeedsInput(id, sessionID);
2319
- tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
2320
- }
2321
- break;
2322
- }
2323
- case "permission.updated": {
2324
- const id = inputRequestID(properties);
2325
- const sessionID = stringProperty(properties, "sessionID");
2326
- const state = inputState(properties);
2327
- if (id && isResolvedInputState(state)) {
2328
- tabTitleManager.clearNeedsInput(id);
2329
- if (sessionID) tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
2330
- } else if (id && sessionID) {
2331
- tabTitleManager.markNeedsInput(id, sessionID);
2332
- tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
2333
- }
2334
- break;
2335
- }
2336
- case "question.replied":
2337
- case "question.rejected":
2338
- case "permission.replied": {
2339
- const id = inputRequestID(properties);
2340
- const sessionID = stringProperty(properties, "sessionID");
2341
- if (id) tabTitleManager.clearNeedsInput(id);
2342
- if (sessionID) tabTitleManager.updateSessionStatus(sessionID, { type: "busy" });
2343
- break;
2344
- }
2345
- case "session.deleted": {
2346
- const sessionID = deletedSessionID(event);
2347
- if (sessionID) tabTitleManager.removeSession(sessionID);
2348
- break;
2349
- }
2350
- }
2351
- }
2352
2297
  //#endregion
2353
2298
  //#region src/zellij/tab-title.ts
2354
2299
  const defaultTabTitleEmojis = {
@@ -2367,12 +2312,240 @@ function sanitizeTitle(title, maxLength = 90) {
2367
2312
  if (chars.length > maxLength) cleaned = `${chars.slice(0, maxLength - 1).join("")}…`;
2368
2313
  return cleaned;
2369
2314
  }
2370
- var TabTitleManager = class {
2315
+ function isRecord(value) {
2316
+ return typeof value === "object" && value !== null;
2317
+ }
2318
+ function stringProperty(object, key) {
2319
+ const value = object[key];
2320
+ return typeof value === "string" ? value : void 0;
2321
+ }
2322
+ function nestedStringProperty(object, key, nestedKey) {
2323
+ const nested = object[key];
2324
+ if (!isRecord(nested)) return void 0;
2325
+ return stringProperty(nested, nestedKey);
2326
+ }
2327
+ function sessionStatusType(properties) {
2328
+ const status = properties.status;
2329
+ if (!isRecord(status)) return void 0;
2330
+ const type = status.type;
2331
+ if (type === "idle" || type === "busy") return type;
2332
+ if (type === "retry") return "retry";
2333
+ }
2334
+ function inputRequestID(properties) {
2335
+ return stringProperty(properties, "id") ?? stringProperty(properties, "requestID") ?? stringProperty(properties, "permissionID");
2336
+ }
2337
+ function inputState(properties) {
2338
+ return (stringProperty(properties, "status") ?? stringProperty(properties, "state") ?? stringProperty(properties, "type"))?.toLowerCase();
2339
+ }
2340
+ function isResolvedInputState(state) {
2341
+ return state === "approved" || state === "denied" || state === "rejected" || state === "resolved" || state === "replied";
2342
+ }
2343
+ function deletedSessionID(properties) {
2344
+ return nestedStringProperty(properties, "info", "id") ?? stringProperty(properties, "sessionID");
2345
+ }
2346
+ var TabTitleIdentityModel = class {
2347
+ ready;
2348
+ projectName;
2349
+ branchName;
2350
+ worktree;
2351
+ readBranch;
2352
+ refreshGeneration = 0;
2353
+ constructor(options) {
2354
+ this.projectName = options.projectName;
2355
+ this.worktree = options.worktree;
2356
+ this.readBranch = options.readBranch;
2357
+ this.ready = this.refreshBranch("initial");
2358
+ }
2359
+ async refreshBranch(_reason) {
2360
+ const generation = ++this.refreshGeneration;
2361
+ try {
2362
+ const result = await this.readBranch(this.worktree);
2363
+ if (generation !== this.refreshGeneration) return;
2364
+ const trimmed = result.trim() || void 0;
2365
+ this.branchName = trimmed;
2366
+ } catch (error) {
2367
+ if (generation !== this.refreshGeneration) return;
2368
+ debug("refreshBranch failed", errorMessage(error));
2369
+ }
2370
+ }
2371
+ handleEvent(event) {
2372
+ if (event.type === "vcs.branch.updated") return this.refreshBranch("vcs.branch.updated");
2373
+ }
2374
+ };
2375
+ var TabTitleActivityModel = class {
2376
+ status = "idle";
2377
+ worktreeDirectory;
2378
+ sessions = /* @__PURE__ */ new Map();
2379
+ scopedSessions = /* @__PURE__ */ new Set();
2371
2380
  runningSessions = /* @__PURE__ */ new Set();
2372
2381
  pendingInputs = /* @__PURE__ */ new Map();
2373
- branchName;
2382
+ constructor(options) {
2383
+ this.worktreeDirectory = options.worktreeDirectory;
2384
+ }
2385
+ getSession(sessionID) {
2386
+ return this.scopedSessions.has(sessionID) ? this.sessions.get(sessionID) : void 0;
2387
+ }
2388
+ hasPendingInput(sessionID, requestID) {
2389
+ return this.pendingInputs.has(`${sessionID}:${requestID}`);
2390
+ }
2391
+ handleEvent(event) {
2392
+ if (!isRecord(event.properties)) return;
2393
+ const properties = event.properties;
2394
+ switch (event.type) {
2395
+ case "session.created":
2396
+ case "session.updated": {
2397
+ const info = properties.info;
2398
+ if (isRecord(info)) {
2399
+ const id = stringProperty(info, "id");
2400
+ if (id) this.storeSession(id, info);
2401
+ }
2402
+ break;
2403
+ }
2404
+ case "session.status": {
2405
+ const sessionID = stringProperty(properties, "sessionID");
2406
+ const statusType = sessionStatusType(properties);
2407
+ if (sessionID && statusType) {
2408
+ if (statusType === "idle") {
2409
+ if (this.runningSessions.has(sessionID)) {
2410
+ this.runningSessions.delete(sessionID);
2411
+ this.updateStatus();
2412
+ }
2413
+ } else if (statusType === "busy" || statusType === "retry") {
2414
+ if (this.scopedSessions.has(sessionID)) {
2415
+ this.runningSessions.add(sessionID);
2416
+ this.updateStatus();
2417
+ }
2418
+ }
2419
+ }
2420
+ break;
2421
+ }
2422
+ case "session.idle":
2423
+ case "session.error": {
2424
+ const sessionID = stringProperty(properties, "sessionID");
2425
+ if (sessionID && this.runningSessions.has(sessionID)) {
2426
+ this.runningSessions.delete(sessionID);
2427
+ this.updateStatus();
2428
+ }
2429
+ break;
2430
+ }
2431
+ case "question.asked":
2432
+ case "permission.asked": {
2433
+ const id = inputRequestID(properties);
2434
+ const sessionID = stringProperty(properties, "sessionID");
2435
+ if (id && sessionID && this.scopedSessions.has(sessionID)) {
2436
+ this.pendingInputs.set(`${sessionID}:${id}`, sessionID);
2437
+ this.runningSessions.add(sessionID);
2438
+ this.updateStatus();
2439
+ }
2440
+ break;
2441
+ }
2442
+ case "permission.updated": {
2443
+ const id = inputRequestID(properties);
2444
+ const sessionID = stringProperty(properties, "sessionID");
2445
+ const state = inputState(properties);
2446
+ if (id && isResolvedInputState(state)) {
2447
+ this.pendingInputs.delete(`${sessionID}:${id}`);
2448
+ if (sessionID && this.runningSessions.has(sessionID)) this.runningSessions.add(sessionID);
2449
+ this.updateStatus();
2450
+ } else if (id && sessionID && this.scopedSessions.has(sessionID)) {
2451
+ this.pendingInputs.set(`${sessionID}:${id}`, sessionID);
2452
+ this.runningSessions.add(sessionID);
2453
+ this.updateStatus();
2454
+ }
2455
+ break;
2456
+ }
2457
+ case "question.replied":
2458
+ case "question.rejected":
2459
+ case "permission.replied": {
2460
+ const id = inputRequestID(properties);
2461
+ const sessionID = stringProperty(properties, "sessionID");
2462
+ if (id) this.pendingInputs.delete(`${sessionID}:${id}`);
2463
+ if (sessionID && this.runningSessions.has(sessionID)) this.runningSessions.add(sessionID);
2464
+ this.updateStatus();
2465
+ break;
2466
+ }
2467
+ case "session.deleted": {
2468
+ const sessionID = deletedSessionID(properties);
2469
+ if (sessionID) {
2470
+ this.removeSessionAndDescendants(sessionID);
2471
+ this.updateStatus();
2472
+ }
2473
+ break;
2474
+ }
2475
+ }
2476
+ }
2477
+ storeSession(id, info) {
2478
+ const directory = stringProperty(info, "directory");
2479
+ const parentID = stringProperty(info, "parentID");
2480
+ this.sessions.set(id, {
2481
+ directory,
2482
+ parentID
2483
+ });
2484
+ const isDirectlyScoped = directory === this.worktreeDirectory;
2485
+ const isDescendantScoped = parentID ? this.scopedSessions.has(parentID) : false;
2486
+ if (this.scopedSessions.has(id) || isDirectlyScoped || isDescendantScoped) this.scopedSessions.add(id);
2487
+ }
2488
+ removeSessionAndDescendants(rootID) {
2489
+ const toRemove = /* @__PURE__ */ new Set();
2490
+ toRemove.add(rootID);
2491
+ let changed = true;
2492
+ while (changed) {
2493
+ changed = false;
2494
+ for (const [id, session] of this.sessions) if (!toRemove.has(id) && session.parentID && toRemove.has(session.parentID)) {
2495
+ toRemove.add(id);
2496
+ changed = true;
2497
+ }
2498
+ }
2499
+ for (const id of toRemove) {
2500
+ this.sessions.delete(id);
2501
+ this.scopedSessions.delete(id);
2502
+ this.runningSessions.delete(id);
2503
+ }
2504
+ for (const [key, sessionID] of [...this.pendingInputs.entries()]) if (toRemove.has(sessionID)) this.pendingInputs.delete(key);
2505
+ }
2506
+ updateStatus() {
2507
+ if (this.pendingInputs.size > 0) this.status = "needs-input";
2508
+ else if (this.runningSessions.size > 0) this.status = "running";
2509
+ else this.status = "idle";
2510
+ }
2511
+ };
2512
+ var TabTitleActor = class {
2513
+ ready;
2514
+ identity;
2515
+ activity;
2516
+ emojis;
2517
+ constructor(options) {
2518
+ this.identity = options.identity;
2519
+ this.activity = options.activity;
2520
+ this.emojis = {
2521
+ ...defaultTabTitleEmojis,
2522
+ ...options.emojis
2523
+ };
2524
+ this.ready = this.identity.ready;
2525
+ }
2526
+ get context() {
2527
+ return {
2528
+ projectName: this.identity.projectName,
2529
+ branchName: this.identity.branchName,
2530
+ status: this.activity.status
2531
+ };
2532
+ }
2533
+ get title() {
2534
+ return formatTabTitle({
2535
+ ...this.context,
2536
+ emojis: this.emojis
2537
+ });
2538
+ }
2539
+ async handleEvent(event) {
2540
+ this.activity.handleEvent(event);
2541
+ const identityResult = this.identity.handleEvent(event);
2542
+ if (identityResult instanceof Promise) await identityResult;
2543
+ }
2544
+ };
2545
+ var TabTitleManager = class {
2374
2546
  desiredTitle;
2375
2547
  lastSyncedTitle;
2548
+ syncGeneration = 0;
2376
2549
  debounceTimer;
2377
2550
  retryTimer;
2378
2551
  retryAttempt = 0;
@@ -2381,7 +2554,6 @@ var TabTitleManager = class {
2381
2554
  debounceMs;
2382
2555
  retryInitialMs;
2383
2556
  retryMaxMs;
2384
- projectName;
2385
2557
  cli;
2386
2558
  emojis;
2387
2559
  enabled;
@@ -2390,9 +2562,8 @@ var TabTitleManager = class {
2390
2562
  originalTabTitleLoaded = false;
2391
2563
  originalTabTitlePromise;
2392
2564
  destroyPromise;
2565
+ actor;
2393
2566
  constructor(options) {
2394
- this.projectName = options.projectName;
2395
- this.branchName = options.branchName?.trim() || void 0;
2396
2567
  this.cli = options.cli ?? new ZellijCli();
2397
2568
  this.emojis = {
2398
2569
  ...defaultTabTitleEmojis,
@@ -2401,65 +2572,12 @@ var TabTitleManager = class {
2401
2572
  this.debounceMs = options.debounceMs ?? 300;
2402
2573
  this.retryInitialMs = options.retryInitialMs ?? 250;
2403
2574
  this.retryMaxMs = options.retryMaxMs ?? 5e3;
2404
- this.enabled = Boolean(process.env.ZELLIJ);
2405
- }
2406
- setBranch(branch) {
2407
- const trimmed = branch?.trim() || void 0;
2408
- if (this.branchName === trimmed) return;
2409
- this.branchName = trimmed;
2410
- this.scheduleUpdate();
2411
- }
2412
- updateSessionStatus(sessionID, status) {
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
- }
2424
- }
2425
- markSessionIdle(sessionID) {
2426
- if (this.runningSessions.delete(sessionID)) this.scheduleUpdate();
2427
- }
2428
- removeSession(sessionID) {
2429
- const hadRunning = this.runningSessions.delete(sessionID);
2430
- let hadPendingInput = false;
2431
- for (const [id, pendingSessionID] of this.pendingInputs) if (pendingSessionID === sessionID) {
2432
- this.pendingInputs.delete(id);
2433
- hadPendingInput = true;
2434
- }
2435
- if (!hadRunning && !hadPendingInput) return;
2436
- this.scheduleUpdate();
2437
- }
2438
- markNeedsInput(id, sessionID) {
2439
- if (this.pendingInputs.get(id) === sessionID) return;
2440
- this.pendingInputs.set(id, sessionID);
2441
- this.scheduleUpdate();
2442
- }
2443
- clearNeedsInput(id) {
2444
- if (!this.pendingInputs.delete(id)) return;
2445
- this.scheduleUpdate();
2446
- }
2447
- get isBusy() {
2448
- return this.runningSessions.size > 0;
2449
- }
2450
- get needsInput() {
2451
- return this.pendingInputs.size > 0;
2452
- }
2453
- get status() {
2454
- if (this.needsInput) return "needs-input";
2455
- if (this.isBusy) return "running";
2456
- return "idle";
2575
+ this.enabled = Boolean(process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME);
2576
+ this.actor = options.actor;
2457
2577
  }
2458
2578
  buildTitle() {
2459
2579
  return sanitizeTitle(formatTabTitle({
2460
- projectName: this.projectName,
2461
- branchName: this.branchName,
2462
- status: this.status,
2580
+ ...this.actor.context,
2463
2581
  emojis: this.emojis
2464
2582
  }));
2465
2583
  }
@@ -2490,24 +2608,27 @@ var TabTitleManager = class {
2490
2608
  }
2491
2609
  async syncDesiredTitle() {
2492
2610
  if (!this.enabled || this.destroyed) return;
2611
+ const generation = this.syncGeneration;
2493
2612
  await this.ensureOriginalTabTitle();
2494
- if (this.destroyed) return;
2613
+ if (this.destroyed || generation !== this.syncGeneration) return;
2495
2614
  if (this.syncInFlight) return this.syncPromise;
2496
2615
  this.syncInFlight = true;
2497
- this.syncPromise = this.runTitleSync();
2616
+ this.syncPromise = this.runTitleSync(generation);
2498
2617
  return this.syncPromise;
2499
2618
  }
2500
- async runTitleSync() {
2619
+ async runTitleSync(generation) {
2501
2620
  try {
2502
- while (this.desiredTitle && this.desiredTitle !== this.lastSyncedTitle) {
2621
+ while (generation === this.syncGeneration && this.desiredTitle && this.desiredTitle !== this.lastSyncedTitle) {
2503
2622
  const title = this.desiredTitle;
2504
2623
  try {
2505
2624
  await this.cli.renameTab(title);
2625
+ if (generation !== this.syncGeneration || this.destroyed) return;
2506
2626
  this.lastSyncedTitle = title;
2507
2627
  this.retryAttempt = 0;
2508
2628
  this.clearRetryTimer();
2509
2629
  } catch (cause) {
2510
2630
  debug("Failed to rename Zellij tab.", cause);
2631
+ if (generation !== this.syncGeneration || this.destroyed) break;
2511
2632
  this.scheduleRetry();
2512
2633
  break;
2513
2634
  }
@@ -2558,6 +2679,8 @@ var TabTitleManager = class {
2558
2679
  destroy() {
2559
2680
  if (this.destroyed) return this.destroyPromise ?? Promise.resolve();
2560
2681
  this.destroyed = true;
2682
+ this.syncGeneration += 1;
2683
+ this.desiredTitle = void 0;
2561
2684
  this.clearDebounceTimer();
2562
2685
  this.clearRetryTimer();
2563
2686
  if (!this.enabled) return Promise.resolve();
@@ -2635,10 +2758,18 @@ function createZellijPtyPlugin(dependencies = {}) {
2635
2758
  registerShutdownCleanup();
2636
2759
  const workspaceRoot = getWorkspaceRoot(input);
2637
2760
  const projectName = getProjectName(workspaceRoot);
2638
- const branchName = config.tabTitle.enabled && shouldReadInitialBranch(process.env.ZELLIJ) ? await getInitialBranch(workspaceRoot) : void 0;
2639
- const tabTitleManager = config.tabTitle.enabled ? new TabTitleManager({
2761
+ const identityModel = config.tabTitle.enabled ? new TabTitleIdentityModel({
2640
2762
  projectName,
2641
- branchName,
2763
+ worktree: workspaceRoot,
2764
+ readBranch: async (worktree) => shouldReadInitialBranch(process.env.ZELLIJ || process.env.ZELLIJ_SESSION_NAME) ? await getInitialBranch(worktree) ?? "" : ""
2765
+ }) : void 0;
2766
+ const activityModel = config.tabTitle.enabled ? new TabTitleActivityModel({ worktreeDirectory: workspaceRoot }) : void 0;
2767
+ const actor = identityModel && activityModel ? new TabTitleActor({
2768
+ identity: identityModel,
2769
+ activity: activityModel
2770
+ }) : void 0;
2771
+ const tabTitleManager = config.tabTitle.enabled && actor ? new TabTitleManager({
2772
+ actor,
2642
2773
  debounceMs: config.tabTitle.debounceMs,
2643
2774
  emojis: {
2644
2775
  idle: config.tabTitle.emojiIdle,
@@ -2672,19 +2803,24 @@ function createZellijPtyPlugin(dependencies = {}) {
2672
2803
  }
2673
2804
  });
2674
2805
  subscriberManager.setLifecycleHooks(completionNotifications ? { onSessionTerminal: (event) => void completionNotifications.handleSessionTerminal(event).catch((error) => debug("completion notification lifecycle hook failed", errorMessage(error))) } : void 0);
2806
+ if (actor) await actor.ready;
2675
2807
  tabTitleManager?.renderImmediate().catch((error) => debug("initial tab title render failed", errorMessage(error)));
2676
2808
  if (config.autoUpdate) (dependencies.startAutoUpdateCheck ?? startAutoUpdateCheck)(client, dependencies.importMetaUrl ?? import.meta.url);
2677
2809
  return {
2678
2810
  async event(input) {
2679
2811
  const event = input.event;
2680
- if (tabTitleManager) await handleTabTitleEvent(tabTitleManager, event);
2812
+ if (actor && tabTitleManager) {
2813
+ await actor.handleEvent(event);
2814
+ if (event.type === "server.instance.disposed" || event.type === "global.disposed") await tabTitleManager.destroy();
2815
+ else tabTitleManager.scheduleUpdate();
2816
+ }
2681
2817
  if (event.type === "server.instance.disposed" || event.type === "global.disposed") {
2682
2818
  completionNotifications?.clearAll();
2683
2819
  completionNotifications?.dispose();
2684
2820
  subscriberManager.setLifecycleHooks(void 0);
2685
2821
  }
2686
2822
  if (event.type === "session.deleted") {
2687
- const sessionID = deletedSessionID(event);
2823
+ const sessionID = deletedSessionID$1(event);
2688
2824
  if (!sessionID) return;
2689
2825
  const sessions = sessionManager.listByOpenCodeSession(sessionID);
2690
2826
  for (const session of sessions) completionNotifications?.clearSession(session.id);