tmux-ide 2.9.15 → 2.9.16

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.
Files changed (26) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +460 -142
  3. package/package.json +2 -1
  4. package/packages/contracts/src/tmux-server-scope.ts +54 -0
  5. package/packages/core/src/terminal-replica.ts +32 -10
  6. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +8 -1
  7. package/packages/daemon/dist/command-center/tmux-servers.js +46 -10
  8. package/packages/daemon/dist/lib/session-favicon.js +152 -0
  9. package/packages/daemon/dist/lib/skill-sync.js +3 -3
  10. package/packages/daemon/dist/lib/tmux-server-owner.js +16 -3
  11. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  12. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  13. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  14. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  15. package/packages/daemon/dist/open-browser.js +47 -3
  16. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +12 -0
  17. package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +16 -0
  18. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +9 -1
  19. package/packages/daemon/src/command-center/tmux-servers.ts +55 -11
  20. package/packages/daemon/src/lib/session-favicon.ts +185 -0
  21. package/packages/daemon/src/lib/skill-sync.ts +3 -3
  22. package/packages/daemon/src/lib/tmux-server-owner.ts +35 -6
  23. package/packages/daemon/src/open-browser.ts +88 -3
  24. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -0
  25. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +25 -0
  26. package/skill/SKILL.md +6 -1
package/bin/cli.js CHANGED
@@ -7432,14 +7432,18 @@ var init_terminal_projection = __esm({
7432
7432
  var tmux_server_scope_exports = {};
7433
7433
  __export(tmux_server_scope_exports, {
7434
7434
  TMUX_SERVERS_API_PATH: () => TMUX_SERVERS_API_PATH,
7435
+ TMUX_SERVER_SESSION_FAVICON_MAX_BYTES: () => TMUX_SERVER_SESSION_FAVICON_MAX_BYTES,
7435
7436
  TmuxServerDescriptorSchemaZ: () => TmuxServerDescriptorSchemaZ,
7436
7437
  TmuxServerGenerationSchemaZ: () => TmuxServerGenerationSchemaZ,
7437
7438
  TmuxServerIdSchemaZ: () => TmuxServerIdSchemaZ,
7438
7439
  TmuxServerPaneTargetSchemaZ: () => TmuxServerPaneTargetSchemaZ,
7439
7440
  TmuxServerRegistrationRequestSchemaZ: () => TmuxServerRegistrationRequestSchemaZ,
7440
7441
  TmuxServerScopeSchemaZ: () => TmuxServerScopeSchemaZ,
7442
+ TmuxServerSessionFaviconMimeSchemaZ: () => TmuxServerSessionFaviconMimeSchemaZ,
7443
+ TmuxServerSessionFaviconSchemaZ: () => TmuxServerSessionFaviconSchemaZ,
7441
7444
  TmuxServerSessionTargetSchemaZ: () => TmuxServerSessionTargetSchemaZ,
7442
7445
  TmuxServerSessionsResourceSchemaZ: () => TmuxServerSessionsResourceSchemaZ,
7446
+ TmuxServerSessionsResourceV2SchemaZ: () => TmuxServerSessionsResourceV2SchemaZ,
7443
7447
  TmuxServerWindowTargetSchemaZ: () => TmuxServerWindowTargetSchemaZ,
7444
7448
  TmuxServersResourceSchemaZ: () => TmuxServersResourceSchemaZ,
7445
7449
  isTmuxServerPaneStreamPath: () => isTmuxServerPaneStreamPath,
@@ -7474,7 +7478,11 @@ function isTmuxServerPaneStreamPath(path2) {
7474
7478
  if (!match) return false;
7475
7479
  return TmuxServerScopeSchemaZ.safeParse({ serverId: match[1], generation: match[2] }).success;
7476
7480
  }
7477
- var descriptorShape, TmuxServerDescriptorSchemaZ, TMUX_SERVERS_API_PATH, TmuxServersResourceSchemaZ, TmuxServerSessionTargetSchemaZ, TmuxServerWindowTargetSchemaZ, TmuxServerPaneTargetSchemaZ, TmuxServerSessionsResourceSchemaZ, TmuxServerRegistrationRequestSchemaZ;
7481
+ function base64DecodedLength(data) {
7482
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
7483
+ return data.length / 4 * 3 - padding;
7484
+ }
7485
+ var descriptorShape, TmuxServerDescriptorSchemaZ, TMUX_SERVERS_API_PATH, TmuxServersResourceSchemaZ, TmuxServerSessionTargetSchemaZ, TmuxServerWindowTargetSchemaZ, TmuxServerPaneTargetSchemaZ, TmuxServerSessionsResourceSchemaZ, TMUX_SERVER_SESSION_FAVICON_MAX_BYTES, TmuxServerSessionFaviconMimeSchemaZ, TmuxServerSessionFaviconSchemaZ, TmuxServerSessionsResourceV2SchemaZ, TmuxServerRegistrationRequestSchemaZ;
7478
7486
  var init_tmux_server_scope = __esm({
7479
7487
  "packages/contracts/src/tmux-server-scope.ts"() {
7480
7488
  "use strict";
@@ -7537,6 +7545,37 @@ var init_tmux_server_scope = __esm({
7537
7545
  }).strict()
7538
7546
  )
7539
7547
  }).strict())();
7548
+ TMUX_SERVER_SESSION_FAVICON_MAX_BYTES = /* @__PURE__ */ (() => 32768)();
7549
+ TmuxServerSessionFaviconMimeSchemaZ = /* @__PURE__ */ (() => z47.enum([
7550
+ "image/png",
7551
+ "image/svg+xml",
7552
+ "image/x-icon",
7553
+ "image/jpeg",
7554
+ "image/webp",
7555
+ "image/gif"
7556
+ ]))();
7557
+ TmuxServerSessionFaviconSchemaZ = /* @__PURE__ */ (() => z47.object({
7558
+ mime: TmuxServerSessionFaviconMimeSchemaZ,
7559
+ data: z47.string().regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u).refine(
7560
+ (data) => base64DecodedLength(data) <= TMUX_SERVER_SESSION_FAVICON_MAX_BYTES,
7561
+ "favicon exceeds 32 KiB"
7562
+ )
7563
+ }).strict())();
7564
+ TmuxServerSessionsResourceV2SchemaZ = /* @__PURE__ */ (() => z47.object({
7565
+ version: z47.literal(2),
7566
+ server: TmuxServerScopeSchemaZ,
7567
+ sessions: z47.array(
7568
+ z47.object({
7569
+ liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
7570
+ sessionName: z47.string().min(1).max(160),
7571
+ workspaceName: z47.string().min(1).max(160).nullable(),
7572
+ paneCount: z47.number().int().nonnegative(),
7573
+ windowCount: z47.number().int().min(1),
7574
+ lastActivity: z47.number().int().nonnegative().nullable(),
7575
+ favicon: TmuxServerSessionFaviconSchemaZ.nullable()
7576
+ }).strict()
7577
+ )
7578
+ }).strict())();
7540
7579
  TmuxServerRegistrationRequestSchemaZ = /* @__PURE__ */ (() => z47.object({
7541
7580
  label: descriptorShape.label.max(128),
7542
7581
  selector: z47.discriminatedUnion("kind", [
@@ -13043,7 +13082,7 @@ var require_package = __commonJS({
13043
13082
  "package.json"(exports, module) {
13044
13083
  module.exports = {
13045
13084
  name: "tmux-ide",
13046
- version: "2.9.15",
13085
+ version: "2.9.16",
13047
13086
  description: "The open-source workspace for coding agents: run Claude Code, Codex and other agents in tmux with live status",
13048
13087
  type: "module",
13049
13088
  bin: {
@@ -13113,6 +13152,7 @@ var require_package = __commonJS({
13113
13152
  postinstall: "node scripts/postinstall.js",
13114
13153
  docs: "turbo run dev --filter=@tmux-ide/docs",
13115
13154
  "demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
13155
+ "demo:themes": "bun --preload @opentui/solid/preload docs/scripts/render-theme-gallery.tsx && node docs/scripts/rasterize-theme-gallery.mjs",
13116
13156
  "demo:font": "node docs/scripts/tui-demo-font/build.mjs",
13117
13157
  "readme:media": "node docs/scripts/readme-media.mjs",
13118
13158
  "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/testing/styled-frame-oracle.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-transient-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-draw-failure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/agent-team-renderer.test.tsx ./scripts/solid-conditional-props.test.tsx",
@@ -39850,7 +39890,7 @@ function createNativeTmuxSessionOpener(options) {
39850
39890
  options.assertOpen();
39851
39891
  if (disposed) throw new Error("Session admission is retired");
39852
39892
  };
39853
- const open4 = async (liveSessionId, observed) => {
39893
+ const open5 = async (liveSessionId, observed) => {
39854
39894
  assertOpen();
39855
39895
  const records = await options.run([
39856
39896
  "list-sessions",
@@ -39912,7 +39952,7 @@ function createNativeTmuxSessionOpener(options) {
39912
39952
  const previous = pending.get(liveSessionId);
39913
39953
  if (previous) return previous;
39914
39954
  if (pending.size >= 16) throw new Error("Session admission capacity reached");
39915
- const operation = tail.then(() => open4(liveSessionId)).finally(() => pending.delete(liveSessionId));
39955
+ const operation = tail.then(() => open5(liveSessionId)).finally(() => pending.delete(liveSessionId));
39916
39956
  pending.set(liveSessionId, operation);
39917
39957
  tail = operation.catch(() => {
39918
39958
  });
@@ -39925,7 +39965,7 @@ function createNativeTmuxSessionOpener(options) {
39925
39965
  if (pending.size >= 16) throw new Error("Session admission capacity reached");
39926
39966
  const key2 = randomUUID23();
39927
39967
  const operation = tail.then(async () => {
39928
- const result2 = await open4(liveSessionId, observed);
39968
+ const result2 = await open5(liveSessionId, observed);
39929
39969
  if (!result2.semanticPaneId) throw new Error("Agent admission returned no target");
39930
39970
  return { ...result2, semanticPaneId: result2.semanticPaneId };
39931
39971
  }).finally(() => pending.delete(key2));
@@ -44563,8 +44603,13 @@ async function applyTerminalReplicaUpdateCooperatively(current, update, options)
44563
44603
  }
44564
44604
  const authenticated = options.authenticatedFrameHash;
44565
44605
  const frameStart = readProfileClock(options.instrumentation);
44566
- const frameHash = authenticated && /^[0-9a-f]{16}$/u.test(authenticated) ? authenticated : await hashCanonicalTerminalValueCooperatively(
44567
- { ...admitted, snapshot: snapshot3 },
44606
+ const reuseAuthenticated = Boolean(authenticated && /^[0-9a-f]{16}$/u.test(authenticated));
44607
+ if (!reuseAuthenticated) {
44608
+ await primeTerminalReplicaRowsHashCooperatively(snapshot3.grid, yieldControl);
44609
+ await primeTerminalReplicaRowsHashCooperatively(snapshot3.history, yieldControl);
44610
+ }
44611
+ const frameHash = reuseAuthenticated ? authenticated : await hashCanonicalTerminalValueCooperatively(
44612
+ terminalReplicaFrameIdentity({ ...admitted, snapshot: snapshot3 }),
44568
44613
  yieldControl,
44569
44614
  // Bound frame hashing without scheduling a task per few cells.
44570
44615
  32 * 1024
@@ -44841,10 +44886,20 @@ function resolveFrameHash(update, options, profile) {
44841
44886
  return authenticated;
44842
44887
  }
44843
44888
  const started = readProfileClock(options.instrumentation);
44844
- const hash = hashStable(update);
44889
+ const hash = hashStable(terminalReplicaFrameIdentity(update));
44845
44890
  addProfileDuration(profile, "updateHash", started, options.instrumentation);
44846
44891
  return hash;
44847
44892
  }
44893
+ function terminalReplicaFrameIdentity(update) {
44894
+ if (update.type !== "terminal.seed") return update;
44895
+ const { grid, history, ...rest } = update.snapshot;
44896
+ return [
44897
+ "terminal-replica-frame-v2",
44898
+ { ...update, snapshot: rest },
44899
+ hashTerminalReplicaRows(grid),
44900
+ hashTerminalReplicaRows(history)
44901
+ ];
44902
+ }
44848
44903
  function readProfileClock(instrumentation) {
44849
44904
  if (!instrumentation) return void 0;
44850
44905
  try {
@@ -62237,6 +62292,14 @@ function joinBytes(chunks) {
62237
62292
  }
62238
62293
  return joined;
62239
62294
  }
62295
+ function logDeliveryFault(client, reason, cause) {
62296
+ logger.info("terminal-delivery", "fault sent to client", {
62297
+ reason,
62298
+ ...cause ? { cause } : {},
62299
+ pane: client.paneId,
62300
+ workspace: client.deliveryWorkspaceName
62301
+ });
62302
+ }
62240
62303
  var MAX_CANONICAL_REVISIONS, MAX_RAW_JOURNAL_BYTES, MAX_REPRESENTATION_CACHE_ENTRIES, MAX_REPRESENTATION_CACHE_BYTES, MAX_CLIENTS, MAX_PANES, MAX_PENDING_CANONICAL_CELLS, MAX_CONNECTIONS, BACKGROUND_CADENCE_MS, DeferredProjectionEncoding, DeferredSeedEncoding, ExactSemanticRepresentationSelectionError, TerminalDeliveryRepresentationSelectionError, TerminalDeliveryPreaccountLimitError, SessionRuntimeTerminalDeliveryHub;
62241
62304
  var init_terminal_delivery_hub = __esm({
62242
62305
  "packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts"() {
@@ -62245,6 +62308,7 @@ var init_terminal_delivery_hub = __esm({
62245
62308
  init_src2();
62246
62309
  init_src3();
62247
62310
  init_stock_capture_fidelity();
62311
+ init_log();
62248
62312
  init_native_seed_backing();
62249
62313
  init_src2();
62250
62314
  init_src3();
@@ -62426,6 +62490,11 @@ var init_terminal_delivery_hub = __esm({
62426
62490
  };
62427
62491
  } catch (error) {
62428
62492
  if (!(error instanceof StockCaptureTabUnavailableError)) throw error;
62493
+ logDeliveryFault(
62494
+ { paneId: semanticPaneId3, deliveryWorkspaceName: address },
62495
+ "source-closed",
62496
+ "capture-unavailable"
62497
+ );
62429
62498
  await accept({
62430
62499
  type: "terminal.delivery.fault",
62431
62500
  reason: "source-closed",
@@ -62584,6 +62653,7 @@ var init_terminal_delivery_hub = __esm({
62584
62653
  this.#cancelEncoding(client);
62585
62654
  client.outgoing.length = 0;
62586
62655
  client.inFlight = null;
62656
+ logDeliveryFault(client, "source-closed", "source-restarted");
62587
62657
  this.#enqueue(client, {
62588
62658
  type: "terminal.delivery.fault",
62589
62659
  reason: "source-closed",
@@ -63571,6 +63641,7 @@ var init_terminal_delivery_hub = __esm({
63571
63641
  this.#recordDeliveryStatus(client, this.#panes.get(client.paneId));
63572
63642
  }
63573
63643
  #fault(client, reason, message, selectionObservation = null) {
63644
+ logDeliveryFault(client, reason);
63574
63645
  if (this.#observability.enabled)
63575
63646
  try {
63576
63647
  const atMicros = this.#observability.nowMicros();
@@ -72667,6 +72738,7 @@ var init_pane_stream_websocket = __esm({
72667
72738
  init_src2();
72668
72739
  init_src2();
72669
72740
  init_src2();
72741
+ init_log();
72670
72742
  init_src2();
72671
72743
  init_admission_util();
72672
72744
  init_registry2();
@@ -73175,6 +73247,11 @@ var init_pane_stream_websocket = __esm({
73175
73247
  this.#open = false;
73176
73248
  this.#cancelDeadline();
73177
73249
  const socket = this.#socket;
73250
+ logger.info("pane-stream", "admission closed", {
73251
+ code: code2,
73252
+ reason,
73253
+ hostClientId: this.hostClientId
73254
+ });
73178
73255
  this.#detach();
73179
73256
  this.#onRelease(this);
73180
73257
  if (socket) safeCloseSocket(socket, code2, reason);
@@ -73426,6 +73503,12 @@ var init_pane_stream_websocket = __esm({
73426
73503
  close(code2 = 1e3, reason = "stream-closed") {
73427
73504
  if (this.#closed) return;
73428
73505
  this.#recordDiagnosticLifecycle("pane-stream-terminal", code2, reason);
73506
+ logger.info("pane-stream", "stream closed", {
73507
+ code: code2,
73508
+ reason,
73509
+ session: this.#descriptor.sessionName,
73510
+ clientId: this.#clientId
73511
+ });
73429
73512
  this.#closed = true;
73430
73513
  for (const demand of this.#surfaceDemands.values()) demand.close();
73431
73514
  this.#surfaceDemands.clear();
@@ -75719,7 +75802,8 @@ function createNativeTmuxServerCatalog(workspaceRegistry, run, assertOpen = () =
75719
75802
  "list-panes",
75720
75803
  "-a",
75721
75804
  "-F",
75722
- "#{pid} #{session_id} #{session_created} #{session_name} #{pane_id} #{session_path}"
75805
+ // session_path stays last: tmux rejects tabs in session names but not in paths.
75806
+ "#{pid} #{session_id} #{session_created} #{session_name} #{pane_id} #{window_id} #{session_activity} #{session_path}"
75723
75807
  ].map(shellEscape).join(" ")
75724
75808
  ],
75725
75809
  AbortSignal.timeout(2e3)
@@ -75728,17 +75812,21 @@ function createNativeTmuxServerCatalog(workspaceRegistry, run, assertOpen = () =
75728
75812
  const rows = /* @__PURE__ */ new Map();
75729
75813
  const lines = raw.trim() === "tmux-ide-empty-catalog-v1" ? [] : raw.trim().split("\n");
75730
75814
  for (const line of lines) {
75731
- const [pid, id2, created, name, pane, dir] = line.split(" ");
75732
- if (!pid || !/^\d+$/.test(pid) || !id2 || !/^\$\d+$/.test(id2) || !created || !/^\d+$/.test(created) || !name || !pane || !/^%\d+$/.test(pane) || !dir)
75815
+ const [pid, id2, created, name, pane, window2, activity, ...pathParts] = line.split(" ");
75816
+ const dir = pathParts.join(" ");
75817
+ if (!pid || !/^\d+$/.test(pid) || !id2 || !/^\$\d+$/.test(id2) || !created || !/^\d+$/.test(created) || !name || !pane || !/^%\d+$/.test(pane) || !window2 || !/^@\d+$/.test(window2) || !dir)
75733
75818
  throw new Error("Invalid native server catalog");
75734
75819
  if (!isVisibleFleetSession(name)) continue;
75735
75820
  const liveSessionId = liveSessionIdForNativeIdentity(pid, id2, created);
75736
75821
  const row = rows.get(liveSessionId) ?? {
75737
75822
  summary: { liveSessionId, sessionName: name, paneCount: 0 },
75738
75823
  panes: /* @__PURE__ */ new Set(),
75739
- dir
75824
+ windows: /* @__PURE__ */ new Set(),
75825
+ dir,
75826
+ activity: activity && /^\d+$/.test(activity) ? Number(activity) : null
75740
75827
  };
75741
75828
  row.panes.add(pane);
75829
+ row.windows.add(window2);
75742
75830
  rows.set(liveSessionId, row);
75743
75831
  }
75744
75832
  const names = new Set([...rows.values()].map(({ summary }) => summary.sessionName));
@@ -75753,7 +75841,13 @@ function createNativeTmuxServerCatalog(workspaceRegistry, run, assertOpen = () =
75753
75841
  projectDir: dir,
75754
75842
  persistence: "volatile"
75755
75843
  });
75756
- return [...rows.values()].map(({ summary, panes }) => ({ ...summary, paneCount: panes.size }));
75844
+ return [...rows.values()].map(({ summary, panes, windows, dir, activity }) => ({
75845
+ ...summary,
75846
+ paneCount: panes.size,
75847
+ windowCount: windows.size,
75848
+ lastActivity: activity,
75849
+ sessionPath: dir
75850
+ }));
75757
75851
  };
75758
75852
  }
75759
75853
  async function createNativeTmuxServerOwner(options) {
@@ -78728,7 +78822,7 @@ function createSharedFleetFactsReaders(run, isAbsentServer = () => false) {
78728
78822
  }
78729
78823
  };
78730
78824
  }
78731
- var DaemonFleetFactsObserver, SESSION_COMPOSITION_TMUX_ARGS, FLEET_FACTS_TMUX_ARGS;
78825
+ var DaemonFleetFactsObserver, FLEET_FACTS_POLL_MS, SESSION_COMPOSITION_TMUX_ARGS, FLEET_FACTS_TMUX_ARGS;
78732
78826
  var init_daemon_fleet_facts_observer = __esm({
78733
78827
  "packages/daemon/src/command-center/daemon-fleet-facts-observer.ts"() {
78734
78828
  "use strict";
@@ -78763,7 +78857,7 @@ var init_daemon_fleet_facts_observer = __esm({
78763
78857
  #diagnosticActiveOperations = 0;
78764
78858
  constructor(options) {
78765
78859
  this.#options = options;
78766
- this.#intervalMs = options.intervalMs ?? 2e3;
78860
+ this.#intervalMs = options.intervalMs ?? FLEET_FACTS_POLL_MS;
78767
78861
  this.#setTimer = options.setTimer ?? ((callback, delayMs) => {
78768
78862
  const timer = setTimeout(callback, delayMs);
78769
78863
  timer.unref?.();
@@ -79101,6 +79195,7 @@ var init_daemon_fleet_facts_observer = __esm({
79101
79195
  return this.#refs.has(demand) && (captured.get(demand) ?? 0) === (this.#demandEpochs.get(demand) ?? 0);
79102
79196
  }
79103
79197
  };
79198
+ FLEET_FACTS_POLL_MS = 500;
79104
79199
  SESSION_COMPOSITION_TMUX_ARGS = [
79105
79200
  "list-panes",
79106
79201
  "-a",
@@ -84761,6 +84856,145 @@ var init_tmux_server_editor_context = __esm({
84761
84856
  }
84762
84857
  });
84763
84858
 
84859
+ // packages/daemon/src/lib/session-favicon.ts
84860
+ import { constants as constants16 } from "node:fs";
84861
+ import { lstat as lstat4, open as open4 } from "node:fs/promises";
84862
+ import { extname, isAbsolute as isAbsolute24, join as join41 } from "node:path";
84863
+ async function lstatOrNull(io, path2) {
84864
+ try {
84865
+ return await io.lstat(path2);
84866
+ } catch {
84867
+ return null;
84868
+ }
84869
+ }
84870
+ function createSessionFaviconResolver(options = {}) {
84871
+ const io = options.io ?? defaultSessionFaviconIo;
84872
+ const now = options.now ?? Date.now;
84873
+ const missTtlMs = options.missTtlMs ?? 3e4;
84874
+ const maxEntries = options.maxEntries ?? 256;
84875
+ const cache3 = /* @__PURE__ */ new Map();
84876
+ const remember = (sessionPath, entry) => {
84877
+ cache3.delete(sessionPath);
84878
+ cache3.set(sessionPath, entry);
84879
+ while (cache3.size > maxEntries) cache3.delete(cache3.keys().next().value);
84880
+ };
84881
+ const scan = async (sessionPath) => {
84882
+ const directories = /* @__PURE__ */ new Map();
84883
+ for (const candidate of SESSION_FAVICON_CANDIDATES) {
84884
+ const mime = MIME_BY_EXTENSION[extname(candidate)];
84885
+ if (!mime) continue;
84886
+ const slash = candidate.lastIndexOf("/");
84887
+ if (slash >= 0) {
84888
+ const parts = candidate.slice(0, slash).split("/");
84889
+ let real = true;
84890
+ for (let index = 1; index <= parts.length && real; index += 1) {
84891
+ const directory2 = parts.slice(0, index).join("/");
84892
+ let known = directories.get(directory2);
84893
+ if (known === void 0) {
84894
+ const stats2 = await lstatOrNull(io, join41(sessionPath, directory2));
84895
+ known = stats2 !== null && stats2.isDirectory() && !stats2.isSymbolicLink();
84896
+ directories.set(directory2, known);
84897
+ }
84898
+ real = known;
84899
+ }
84900
+ if (!real) continue;
84901
+ }
84902
+ const file = join41(sessionPath, candidate);
84903
+ const stats = await lstatOrNull(io, file);
84904
+ if (!stats || !stats.isFile() || stats.size > TMUX_SERVER_SESSION_FAVICON_MAX_BYTES) continue;
84905
+ const bytes = await io.readNoFollow(file, TMUX_SERVER_SESSION_FAVICON_MAX_BYTES);
84906
+ if (!bytes) continue;
84907
+ return {
84908
+ kind: "hit",
84909
+ file,
84910
+ mtimeMs: stats.mtimeMs,
84911
+ size: stats.size,
84912
+ favicon: { mime, data: bytes.toString("base64"), size: bytes.byteLength }
84913
+ };
84914
+ }
84915
+ return { kind: "miss", at: now() };
84916
+ };
84917
+ return async (sessionPath) => {
84918
+ if (!sessionPath || !isAbsolute24(sessionPath) || sessionPath.includes("\0")) return null;
84919
+ const cached2 = cache3.get(sessionPath);
84920
+ if (cached2?.kind === "miss" && now() - cached2.at < missTtlMs) {
84921
+ remember(sessionPath, cached2);
84922
+ return null;
84923
+ }
84924
+ if (cached2?.kind === "hit") {
84925
+ const stats = await lstatOrNull(io, cached2.file);
84926
+ if (stats && stats.isFile() && stats.mtimeMs === cached2.mtimeMs && stats.size === cached2.size) {
84927
+ remember(sessionPath, cached2);
84928
+ return cached2.favicon;
84929
+ }
84930
+ }
84931
+ const entry = await scan(sessionPath);
84932
+ remember(sessionPath, entry);
84933
+ return entry.kind === "hit" ? entry.favicon : null;
84934
+ };
84935
+ }
84936
+ var SESSION_FAVICON_CANDIDATES, MIME_BY_EXTENSION, defaultSessionFaviconIo;
84937
+ var init_session_favicon = __esm({
84938
+ "packages/daemon/src/lib/session-favicon.ts"() {
84939
+ "use strict";
84940
+ init_src2();
84941
+ SESSION_FAVICON_CANDIDATES = [
84942
+ "favicon.svg",
84943
+ "favicon.png",
84944
+ "favicon.ico",
84945
+ "public/favicon.svg",
84946
+ "public/favicon.png",
84947
+ "public/favicon.ico",
84948
+ "app/icon.svg",
84949
+ "app/icon.png",
84950
+ "app/favicon.ico",
84951
+ "src/app/icon.svg",
84952
+ "src/app/icon.png",
84953
+ "src/app/favicon.ico",
84954
+ "static/favicon.svg",
84955
+ "static/favicon.png",
84956
+ "static/favicon.ico",
84957
+ "assets/favicon.svg",
84958
+ "assets/favicon.png",
84959
+ "assets/favicon.ico",
84960
+ "apple-touch-icon.png",
84961
+ "public/apple-touch-icon.png",
84962
+ "icon.svg",
84963
+ "icon.png"
84964
+ ];
84965
+ MIME_BY_EXTENSION = {
84966
+ ".png": "image/png",
84967
+ ".svg": "image/svg+xml",
84968
+ ".ico": "image/x-icon",
84969
+ ".jpg": "image/jpeg",
84970
+ ".jpeg": "image/jpeg",
84971
+ ".webp": "image/webp",
84972
+ ".gif": "image/gif"
84973
+ };
84974
+ defaultSessionFaviconIo = {
84975
+ lstat: (path2) => lstat4(path2),
84976
+ readNoFollow: async (path2, maxBytes) => {
84977
+ let handle;
84978
+ try {
84979
+ handle = await open4(path2, constants16.O_RDONLY | constants16.O_NOFOLLOW | constants16.O_NONBLOCK);
84980
+ } catch {
84981
+ return null;
84982
+ }
84983
+ try {
84984
+ const stats = await handle.stat();
84985
+ if (!stats.isFile() || stats.size > maxBytes) return null;
84986
+ const bytes = await handle.readFile();
84987
+ return bytes.byteLength > maxBytes ? null : bytes;
84988
+ } catch {
84989
+ return null;
84990
+ } finally {
84991
+ await handle.close().catch(() => void 0);
84992
+ }
84993
+ }
84994
+ };
84995
+ }
84996
+ });
84997
+
84764
84998
  // packages/daemon/src/command-center/pane-stream-issue.ts
84765
84999
  function issueError(code2, reason, retryable = false) {
84766
85000
  return PaneStreamIssueResultSchemaZ.parse({
@@ -85019,6 +85253,7 @@ function failure(c, error) {
85019
85253
  }
85020
85254
  function mountTmuxServerRoutes(app, options) {
85021
85255
  const base2 = TMUX_SERVERS_API_PATH;
85256
+ const resolveSessionFavicon = options.resolveSessionFavicon ?? createSessionFaviconResolver();
85022
85257
  const authorize = async (c, next) => {
85023
85258
  c.header("Cache-Control", "no-store");
85024
85259
  if (!ownerBearerMatches(c.req.header("Authorization"), options.ownerToken))
@@ -85191,19 +85426,54 @@ function mountTmuxServerRoutes(app, options) {
85191
85426
  `${scoped}/sessions`,
85192
85427
  route(async (c) => {
85193
85428
  const server = scope2(c);
85429
+ const v2 = c.req.query("version") === "2";
85430
+ const { rows, favicons } = await options.owners.withOwner(server, async (owner) => {
85431
+ const sessions = await owner.catalog();
85432
+ const workspaces = owner.workspaceRegistry.list();
85433
+ const rows2 = sessions.map((session) => {
85434
+ const aliases = workspaces.filter(
85435
+ (workspace) => workspace.sessionName === session.sessionName
85436
+ );
85437
+ return { session, workspaceName: aliases.length === 1 ? aliases[0].name : null };
85438
+ });
85439
+ const favicons2 = v2 ? await Promise.all(
85440
+ sessions.map(
85441
+ (session) => resolveSessionFavicon(session.sessionPath).catch(() => null)
85442
+ )
85443
+ ) : [];
85444
+ return { rows: rows2, favicons: favicons2 };
85445
+ });
85446
+ if (!v2)
85447
+ return c.json(
85448
+ TmuxServerSessionsResourceSchemaZ.parse({
85449
+ version: 1,
85450
+ server,
85451
+ sessions: rows.map(({ session, workspaceName }) => ({
85452
+ liveSessionId: session.liveSessionId,
85453
+ sessionName: session.sessionName,
85454
+ paneCount: session.paneCount,
85455
+ workspaceName
85456
+ }))
85457
+ })
85458
+ );
85459
+ let budget2 = SESSIONS_RESOURCE_FAVICON_BUDGET_BYTES;
85194
85460
  return c.json(
85195
- TmuxServerSessionsResourceSchemaZ.parse({
85196
- version: 1,
85461
+ TmuxServerSessionsResourceV2SchemaZ.parse({
85462
+ version: 2,
85197
85463
  server,
85198
- sessions: await options.owners.withOwner(server, async (owner) => {
85199
- const sessions = await owner.catalog();
85200
- const workspaces = owner.workspaceRegistry.list();
85201
- return sessions.map((session) => {
85202
- const aliases = workspaces.filter(
85203
- (workspace) => workspace.sessionName === session.sessionName
85204
- );
85205
- return { ...session, workspaceName: aliases.length === 1 ? aliases[0].name : null };
85206
- });
85464
+ sessions: rows.map(({ session, workspaceName }, index) => {
85465
+ const found = favicons[index] ?? null;
85466
+ const favicon = found && found.size <= budget2 ? found : null;
85467
+ if (favicon) budget2 -= favicon.size;
85468
+ return {
85469
+ liveSessionId: session.liveSessionId,
85470
+ sessionName: session.sessionName,
85471
+ workspaceName,
85472
+ paneCount: session.paneCount,
85473
+ windowCount: session.windowCount,
85474
+ lastActivity: session.lastActivity,
85475
+ favicon: favicon ? { mime: favicon.mime, data: favicon.data } : null
85476
+ };
85207
85477
  })
85208
85478
  })
85209
85479
  );
@@ -85469,7 +85739,7 @@ function mountTmuxServerRoutes(app, options) {
85469
85739
  })
85470
85740
  );
85471
85741
  }
85472
- var registrationSchema, workspaceSchema;
85742
+ var SESSIONS_RESOURCE_FAVICON_BUDGET_BYTES, registrationSchema, workspaceSchema;
85473
85743
  var init_tmux_servers = __esm({
85474
85744
  "packages/daemon/src/command-center/tmux-servers.ts"() {
85475
85745
  "use strict";
@@ -85484,11 +85754,13 @@ var init_tmux_servers = __esm({
85484
85754
  init_fleet_catalog2();
85485
85755
  init_src2();
85486
85756
  init_tmux_server_owners();
85757
+ init_session_favicon();
85487
85758
  init_workspace_multiplexer_verbs();
85488
85759
  init_workspace_pane_creation2();
85489
85760
  init_owner_authority();
85490
85761
  init_pane_stream_issue();
85491
85762
  init_terminal_runtime_inventory2();
85763
+ SESSIONS_RESOURCE_FAVICON_BUDGET_BYTES = 512 * 1024;
85492
85764
  registrationSchema = TmuxServerRegistrationSchemaZ.omit({ serverId: true }).strict();
85493
85765
  workspaceSchema = z125.string().min(1).max(160).refine((value) => !/[\0\r\n]/u.test(value));
85494
85766
  }
@@ -85665,9 +85937,9 @@ import {
85665
85937
  writeFileSync as writeFileSync22,
85666
85938
  watch
85667
85939
  } from "node:fs";
85668
- import { basename as basename15, dirname as dirname33, join as join41 } from "node:path";
85940
+ import { basename as basename15, dirname as dirname33, join as join42 } from "node:path";
85669
85941
  function savedMachinesPath() {
85670
- return runtimeOwnedPath(join41(resolveRuntimeNamespace().registryDir, "machines.json"));
85942
+ return runtimeOwnedPath(join42(resolveRuntimeNamespace().registryDir, "machines.json"));
85671
85943
  }
85672
85944
  function loadSavedMachines(path2 = savedMachinesPath()) {
85673
85945
  let fd;
@@ -85986,9 +86258,9 @@ import {
85986
86258
  writeFileSync as writeFileSync23
85987
86259
  } from "node:fs";
85988
86260
  import { randomUUID as randomUUID43 } from "node:crypto";
85989
- import { dirname as dirname34, join as join42 } from "node:path";
86261
+ import { dirname as dirname34, join as join43 } from "node:path";
85990
86262
  function fleetClientStatePath() {
85991
- return runtimeOwnedPath(join42(resolveRuntimeNamespace().registryDir, "fleet-view.json"));
86263
+ return runtimeOwnedPath(join43(resolveRuntimeNamespace().registryDir, "fleet-view.json"));
85992
86264
  }
85993
86265
  function loadFleetClientState(path2 = fleetClientStatePath()) {
85994
86266
  let fd;
@@ -86221,14 +86493,14 @@ var init_schemas = __esm({
86221
86493
  import { createHash as createHash28 } from "node:crypto";
86222
86494
  import { realpathSync as realpathSync21 } from "node:fs";
86223
86495
  import { existsSync as existsSync28, mkdirSync as mkdirSync27, readFileSync as readFileSync29, renameSync as renameSync17, writeFileSync as writeFileSync24 } from "node:fs";
86224
- import { dirname as dirname35, join as join43 } from "node:path";
86496
+ import { dirname as dirname35, join as join44 } from "node:path";
86225
86497
  function path(dir) {
86226
86498
  const namespace = resolveRuntimeNamespace();
86227
86499
  if (namespace.development) {
86228
86500
  const identity4 = createHash28("sha256").update(realpathSync21(dir)).digest("hex");
86229
- return runtimeOwnedPath(join43(namespace.stateHome, "projects", identity4, "terminals.json"));
86501
+ return runtimeOwnedPath(join44(namespace.stateHome, "projects", identity4, "terminals.json"));
86230
86502
  }
86231
- return join43(dir, TERMINALS_FILE);
86503
+ return join44(dir, TERMINALS_FILE);
86232
86504
  }
86233
86505
  function ensureDir(dir) {
86234
86506
  mkdirSync27(dirname35(path(dir)), { recursive: true });
@@ -86319,7 +86591,7 @@ __export(auth_service_exports, {
86319
86591
  });
86320
86592
  import * as crypto2 from "node:crypto";
86321
86593
  import { readFileSync as readFileSync30, existsSync as existsSync29 } from "node:fs";
86322
- import { join as join44 } from "node:path";
86594
+ import { join as join45 } from "node:path";
86323
86595
  import { homedir as homedir5 } from "node:os";
86324
86596
  function base64url(buf) {
86325
86597
  const b = typeof buf === "string" ? Buffer.from(buf) : buf;
@@ -86465,7 +86737,7 @@ var init_auth_service = __esm({
86465
86737
  if (resolveRuntimeNamespace().development) return false;
86466
86738
  try {
86467
86739
  const home = userId === process.env.USER ? homedir5() : `/home/${userId}`;
86468
- const authKeysPath = join44(home, ".ssh", "authorized_keys");
86740
+ const authKeysPath = join45(home, ".ssh", "authorized_keys");
86469
86741
  if (!existsSync29(authKeysPath)) return false;
86470
86742
  const authorizedKeys = readFileSync30(authKeysPath, "utf-8");
86471
86743
  const parts = publicKey.trim().split(" ");
@@ -89491,7 +89763,7 @@ var init_inspect = __esm({
89491
89763
  // packages/daemon/src/lib/filesystem-browser.ts
89492
89764
  import { realpathSync as realpathSync22, readdirSync as readdirSync6, statSync as statSync19 } from "node:fs";
89493
89765
  import { homedir as homedir6 } from "node:os";
89494
- import { isAbsolute as isAbsolute24, join as join45, resolve as resolve32, sep as sep11 } from "node:path";
89766
+ import { isAbsolute as isAbsolute25, join as join46, resolve as resolve32, sep as sep11 } from "node:path";
89495
89767
  function isUnderRoot(canonical, root) {
89496
89768
  if (canonical === root) return true;
89497
89769
  const prefix = root.endsWith(sep11) ? root : root + sep11;
@@ -89735,7 +90007,7 @@ var init_detect = __esm({
89735
90007
 
89736
90008
  // packages/daemon/src/lib/project-inspect.ts
89737
90009
  import { existsSync as existsSync32 } from "node:fs";
89738
- import { isAbsolute as isAbsolute25, resolve as resolve34 } from "node:path";
90010
+ import { isAbsolute as isAbsolute26, resolve as resolve34 } from "node:path";
89739
90011
  function narrowPackageManager(raw) {
89740
90012
  if (!raw) return null;
89741
90013
  return KNOWN_PACKAGE_MANAGERS.has(raw) ? raw : null;
@@ -89746,7 +90018,7 @@ function inferTestCommand(packageManager) {
89746
90018
  }
89747
90019
  async function inspectProject(dir, io = {}) {
89748
90020
  const exists = io.exists ?? existsSync32;
89749
- const absoluteDir = isAbsolute25(dir) ? dir : resolve34(dir);
90021
+ const absoluteDir = isAbsolute26(dir) ? dir : resolve34(dir);
89750
90022
  if (!exists(absoluteDir)) {
89751
90023
  throw new InspectDirNotFoundError(absoluteDir);
89752
90024
  }
@@ -90606,7 +90878,7 @@ var init_workspace_changes_git = __esm({
90606
90878
  // packages/daemon/src/command-center/resources/workspace-changes-authority.ts
90607
90879
  import { spawnSync } from "node:child_process";
90608
90880
  import { readFileSync as readFileSync33, realpathSync as realpathSync24, statSync as statSync20 } from "node:fs";
90609
- import { basename as basename18, isAbsolute as isAbsolute26, relative as relative8, resolve as resolvePath2 } from "node:path";
90881
+ import { basename as basename18, isAbsolute as isAbsolute27, relative as relative8, resolve as resolvePath2 } from "node:path";
90610
90882
  function runGit(args, cwd) {
90611
90883
  const result2 = spawnSync("git", args, {
90612
90884
  cwd,
@@ -90650,7 +90922,7 @@ function confineToWorkspace(realRoot, repoRoot, gitPath) {
90650
90922
  if (gitPath.length === 0 || gitPath.includes("\0")) return null;
90651
90923
  const abs = resolvePath2(repoRoot, gitPath);
90652
90924
  const rel = relative8(realRoot, abs);
90653
- if (rel.length === 0 || rel.startsWith("..") || isAbsolute26(rel)) return null;
90925
+ if (rel.length === 0 || rel.startsWith("..") || isAbsolute27(rel)) return null;
90654
90926
  const display = rel.split(/[\\/]+/u).join("/");
90655
90927
  return WorkspaceRelativeDisplayPathSchemaZ.safeParse(display).success ? display : null;
90656
90928
  }
@@ -92878,9 +93150,9 @@ import {
92878
93150
  rmSync as rmSync5,
92879
93151
  writeFileSync as writeFileSync25
92880
93152
  } from "node:fs";
92881
- import { join as join46 } from "node:path";
93153
+ import { join as join47 } from "node:path";
92882
93154
  function assetRoot() {
92883
- return runtimeOwnedPath(join46(stateHome(), ASSET_DIRECTORY));
93155
+ return runtimeOwnedPath(join47(stateHome(), ASSET_DIRECTORY));
92884
93156
  }
92885
93157
  function ensureAssetRoot() {
92886
93158
  const root = assetRoot();
@@ -92900,8 +93172,8 @@ function safeName2(name) {
92900
93172
  }
92901
93173
  function assetPaths(root, assetId) {
92902
93174
  return {
92903
- data: join46(root, `${assetId}.bin`),
92904
- metadata: join46(root, `${assetId}.json`)
93175
+ data: join47(root, `${assetId}.bin`),
93176
+ metadata: join47(root, `${assetId}.json`)
92905
93177
  };
92906
93178
  }
92907
93179
  function parseMetadata(raw) {
@@ -92926,7 +93198,7 @@ function parseMetadata(raw) {
92926
93198
  }
92927
93199
  function pruneAssets(root, now = Date.now()) {
92928
93200
  const metadataFiles = readdirSync8(root).filter((name) => /^[0-9a-f]{64}\.json$/u.test(name)).map((name) => {
92929
- const path2 = join46(root, name);
93201
+ const path2 = join47(root, name);
92930
93202
  try {
92931
93203
  const stat3 = lstatSync14(path2);
92932
93204
  return stat3.isFile() && !stat3.isSymbolicLink() ? { name, mtimeMs: stat3.mtimeMs } : null;
@@ -92937,8 +93209,8 @@ function pruneAssets(root, now = Date.now()) {
92937
93209
  for (const [index, entry] of metadataFiles.entries()) {
92938
93210
  if (index < MAX_ASSET_FILES && now - entry.mtimeMs <= WIDGET_ASSET_RETENTION_MS) continue;
92939
93211
  const assetId = entry.name.slice(0, -".json".length);
92940
- rmSync5(join46(root, `${assetId}.json`), { force: true });
92941
- rmSync5(join46(root, `${assetId}.bin`), { force: true });
93212
+ rmSync5(join47(root, `${assetId}.json`), { force: true });
93213
+ rmSync5(join47(root, `${assetId}.bin`), { force: true });
92942
93214
  }
92943
93215
  }
92944
93216
  function publishWidgetAsset(bytes, options) {
@@ -92967,11 +93239,11 @@ function publishWidgetAsset(bytes, options) {
92967
93239
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
92968
93240
  };
92969
93241
  if (!existsSync33(paths.data)) {
92970
- const temporary = join46(root, `.${assetId}.${randomUUID45()}.bin`);
93242
+ const temporary = join47(root, `.${assetId}.${randomUUID45()}.bin`);
92971
93243
  writeFileSync25(temporary, bytes, { mode: 384, flag: "wx" });
92972
93244
  renameSync18(temporary, paths.data);
92973
93245
  }
92974
- const metadataTemporary = join46(root, `.${assetId}.${randomUUID45()}.json`);
93246
+ const metadataTemporary = join47(root, `.${assetId}.${randomUUID45()}.json`);
92975
93247
  writeFileSync25(metadataTemporary, `${JSON.stringify(metadata)}
92976
93248
  `, { mode: 384, flag: "wx" });
92977
93249
  renameSync18(metadataTemporary, paths.metadata);
@@ -93033,7 +93305,7 @@ __export(server_exports2, {
93033
93305
  import { execFile as execFile14 } from "node:child_process";
93034
93306
  import { promisify as promisify4 } from "node:util";
93035
93307
  import { existsSync as existsSync34, readdirSync as readdirSync9 } from "node:fs";
93036
- import { join as join47, dirname as dirname38, basename as basename19 } from "node:path";
93308
+ import { join as join48, dirname as dirname38, basename as basename19 } from "node:path";
93037
93309
  import { fileURLToPath as fileURLToPath10 } from "node:url";
93038
93310
  import { Hono as Hono3 } from "hono";
93039
93311
  import { streamSSE as streamSSE3 } from "hono/streaming";
@@ -93042,7 +93314,7 @@ import { zValidator } from "@hono/zod-validator";
93042
93314
  import { z as z130 } from "zod";
93043
93315
  import { realpathSync as realpathSync25 } from "node:fs";
93044
93316
  import { homedir as homedir7 } from "node:os";
93045
- import { isAbsolute as isAbsolute27, resolve as pathResolve } from "node:path";
93317
+ import { isAbsolute as isAbsolute28, resolve as pathResolve } from "node:path";
93046
93318
  import { randomUUID as randomUUID46 } from "node:crypto";
93047
93319
  import { WebSocketServer as WebSocketServer4 } from "ws";
93048
93320
  function bearerToken(authHeader) {
@@ -93148,7 +93420,7 @@ function sandboxResolveDir(rawDir) {
93148
93420
  } else if (candidate.startsWith("~/")) {
93149
93421
  candidate = `${home.replace(/\/+$/, "")}/${candidate.slice(2)}`;
93150
93422
  }
93151
- if (!isAbsolute27(candidate)) {
93423
+ if (!isAbsolute28(candidate)) {
93152
93424
  return { error: "invalid-path", message: "Path must be absolute", status: 400 };
93153
93425
  }
93154
93426
  const resolved2 = pathResolve(candidate);
@@ -94285,7 +94557,7 @@ function listAvailableTemplates() {
94285
94557
  const __filename = fileURLToPath10(import.meta.url);
94286
94558
  const __dir = dirname38(__filename);
94287
94559
  const configuredTemplatesDir = process.env.TMUX_IDE_TEMPLATES_DIR;
94288
- const templatesDir = configuredTemplatesDir && isAbsolute27(configuredTemplatesDir) ? configuredTemplatesDir : join47(__dir, "..", "..", "..", "..", "templates");
94560
+ const templatesDir = configuredTemplatesDir && isAbsolute28(configuredTemplatesDir) ? configuredTemplatesDir : join48(__dir, "..", "..", "..", "..", "templates");
94289
94561
  if (!existsSync34(templatesDir)) return [];
94290
94562
  const labels = {
94291
94563
  default: { label: "Default", description: "Single Claude pane + dev/shell row" },
@@ -94482,7 +94754,7 @@ var init_types = __esm({
94482
94754
  });
94483
94755
 
94484
94756
  // packages/daemon/src/lib/daemon-embed.ts
94485
- import { dirname as dirname39, join as join48 } from "node:path";
94757
+ import { dirname as dirname39, join as join49 } from "node:path";
94486
94758
  import { execFileSync as execFileSync21 } from "node:child_process";
94487
94759
  import { randomBytes as randomBytes9, randomUUID as randomUUID47 } from "node:crypto";
94488
94760
  import { createWriteStream } from "node:fs";
@@ -95158,7 +95430,7 @@ async function startEmbeddedDaemonGeneration(opts) {
95158
95430
  execFileSync21(tmuxAuthority.executablePath, ["-V"], { encoding: "utf8", timeout: 3e3 })
95159
95431
  );
95160
95432
  const readTeamMemberships = createClaudeTeamMembershipReader(
95161
- join48(resolveRuntimeNamespace().claudeDir, "teams")
95433
+ join49(resolveRuntimeNamespace().claudeDir, "teams")
95162
95434
  );
95163
95435
  const catalogTmuxRunner = createPinnedWorkspaceTmuxRunner(tmuxAuthority);
95164
95436
  const initialTmuxProof = captureTmuxServerProof(catalogTmuxRunner);
@@ -96825,7 +97097,7 @@ __export(development_owner_exports, {
96825
97097
  verifyManagedDevelopmentAdmission: () => verifyManagedDevelopmentAdmission
96826
97098
  });
96827
97099
  import { realpathSync as realpathSync26, writeFileSync as writeFileSync26 } from "node:fs";
96828
- import { join as join49 } from "node:path";
97100
+ import { join as join50 } from "node:path";
96829
97101
  async function claimManagedDevelopmentLaunch(instance, attempt) {
96830
97102
  if (!/^[a-f0-9-]{36}$/u.test(attempt)) throw new Error("Invalid development launch attempt");
96831
97103
  const owner = readDevelopmentOwner(instance);
@@ -96833,7 +97105,7 @@ async function claimManagedDevelopmentLaunch(instance, attempt) {
96833
97105
  throw new Error("A live or unknown managed owner is protected");
96834
97106
  requireDevelopmentNotSuspended(instance);
96835
97107
  writeFileSync26(
96836
- join49(instance.root, `launch-${attempt}.json`),
97108
+ join50(instance.root, `launch-${attempt}.json`),
96837
97109
  JSON.stringify({ version: 1, attempt, pid: process.pid }),
96838
97110
  { flag: "wx", mode: 384 }
96839
97111
  );
@@ -96842,7 +97114,7 @@ async function claimManagedDevelopmentLaunch(instance, attempt) {
96842
97114
  function verifyManagedDevelopmentAdmission(instance, attempt, expected) {
96843
97115
  requireDevelopmentNotSuspended(instance);
96844
97116
  const current = readPrivateDevelopmentRecord(
96845
- join49(instance.root, "startup.json")
97117
+ join50(instance.root, "startup.json")
96846
97118
  );
96847
97119
  if (!current || current.attempt !== attempt || current.attempt !== expected.attempt || current.generation !== expected.generation || current.manifestHash !== expected.manifestHash)
96848
97120
  throw new Error("Managed owner admission changed before startup");
@@ -96853,7 +97125,7 @@ async function runManagedDevelopmentOwner() {
96853
97125
  if (!instance) throw new Error("Managed development owner requires a development namespace");
96854
97126
  const identity4 = await readDevelopmentIdentity(instance);
96855
97127
  const attempt = process.env.TMUX_IDE_DEVELOPMENT_ATTEMPT;
96856
- const pending = readPrivateDevelopmentRecord(join49(instance.root, "startup.json"));
97128
+ const pending = readPrivateDevelopmentRecord(join50(instance.root, "startup.json"));
96857
97129
  const build = readDevelopmentBuild(instance);
96858
97130
  if (!identity4 || identity4.capability !== namespace.cleanupToken || !attempt || pending?.attempt !== attempt || pending.generation !== build.generation || pending.manifestHash !== process.env.TMUX_IDE_DEVELOPMENT_BUILD_HASH)
96859
97131
  throw new Error("Development launch admission does not match owner");
@@ -96863,7 +97135,7 @@ async function runManagedDevelopmentOwner() {
96863
97135
  if (!incarnation) throw new Error("Cannot establish managed owner incarnation");
96864
97136
  await claimManagedDevelopmentLaunch(instance, attempt);
96865
97137
  verifyManagedDevelopmentAdmission(instance, attempt, pending);
96866
- const log = createBoundedDevelopmentLog(join49(instance.root, "logs/owner.log"));
97138
+ const log = createBoundedDevelopmentLog(join50(instance.root, "logs/owner.log"));
96867
97139
  const originalOut = process.stdout.write;
96868
97140
  const originalErr = process.stderr.write;
96869
97141
  const writer = ((chunk, encodingOrCallback, callback) => {
@@ -96880,7 +97152,7 @@ async function runManagedDevelopmentOwner() {
96880
97152
  json: true,
96881
97153
  expectedVersion: build.packageVersion,
96882
97154
  onOwnedReady: () => {
96883
- writeDevelopmentRecord(join49(instance.root, "owner.json"), {
97155
+ writeDevelopmentRecord(join50(instance.root, "owner.json"), {
96884
97156
  version: 1,
96885
97157
  attempt,
96886
97158
  pid: process.pid,
@@ -96927,7 +97199,7 @@ import {
96927
97199
  readdirSync as readdirSync10,
96928
97200
  copyFileSync as copyFileSync2
96929
97201
  } from "node:fs";
96930
- import { resolve as resolve35, join as join50, basename as basename20, dirname as dirname40 } from "node:path";
97202
+ import { resolve as resolve35, join as join51, basename as basename20, dirname as dirname40 } from "node:path";
96931
97203
  import { fileURLToPath as fileURLToPath11 } from "node:url";
96932
97204
  function copyTemplateSkills(targetDir) {
96933
97205
  const created = [];
@@ -96936,20 +97208,20 @@ function copyTemplateSkills(targetDir) {
96936
97208
  mkdirSync29(targetDir, { recursive: true });
96937
97209
  for (const file of readdirSync10(templateSkillsDir)) {
96938
97210
  if (!file.endsWith(".md")) continue;
96939
- const destination = join50(targetDir, file);
96940
- copyFileSync2(join50(templateSkillsDir, file), destination);
97211
+ const destination = join51(targetDir, file);
97212
+ copyFileSync2(join51(templateSkillsDir, file), destination);
96941
97213
  created.push(destination);
96942
97214
  }
96943
97215
  return created;
96944
97216
  }
96945
97217
  function scaffoldLibraryStubs(dir) {
96946
97218
  const created = [];
96947
- const libraryDir = join50(dir, ".tmux-ide", "library");
97219
+ const libraryDir = join51(dir, ".tmux-ide", "library");
96948
97220
  if (!existsSync35(libraryDir)) {
96949
97221
  mkdirSync29(libraryDir, { recursive: true });
96950
97222
  created.push(libraryDir);
96951
97223
  }
96952
- const archPath = join50(libraryDir, "architecture.md");
97224
+ const archPath = join51(libraryDir, "architecture.md");
96953
97225
  if (!existsSync35(archPath)) {
96954
97226
  writeFileSync27(
96955
97227
  archPath,
@@ -96957,7 +97229,7 @@ function scaffoldLibraryStubs(dir) {
96957
97229
  );
96958
97230
  created.push(archPath);
96959
97231
  }
96960
- const learningsPath = join50(libraryDir, "learnings.md");
97232
+ const learningsPath = join51(libraryDir, "learnings.md");
96961
97233
  if (!existsSync35(learningsPath)) {
96962
97234
  writeFileSync27(
96963
97235
  learningsPath,
@@ -96969,11 +97241,11 @@ function scaffoldLibraryStubs(dir) {
96969
97241
  }
96970
97242
  function scaffoldValidationContract(dir) {
96971
97243
  const created = [];
96972
- const tasksDir = join50(dir, ".tasks");
97244
+ const tasksDir = join51(dir, ".tasks");
96973
97245
  if (!existsSync35(tasksDir)) {
96974
97246
  mkdirSync29(tasksDir, { recursive: true });
96975
97247
  }
96976
- const contractPath = join50(tasksDir, "validation-contract.md");
97248
+ const contractPath = join51(tasksDir, "validation-contract.md");
96977
97249
  if (!existsSync35(contractPath)) {
96978
97250
  writeFileSync27(
96979
97251
  contractPath,
@@ -96987,7 +97259,7 @@ function scaffoldAgentsMd(dir, name) {
96987
97259
  const created = [];
96988
97260
  const agentsTemplatePath = resolve35(__dirname4, "..", "..", "..", "templates", "AGENTS.md");
96989
97261
  if (existsSync35(agentsTemplatePath)) {
96990
- const agentsPath = join50(dir, "AGENTS.md");
97262
+ const agentsPath = join51(dir, "AGENTS.md");
96991
97263
  if (!existsSync35(agentsPath)) {
96992
97264
  const content = readFileSync35(agentsTemplatePath, "utf-8").replace(/{{name}}/g, name);
96993
97265
  writeFileSync27(agentsPath, content);
@@ -97008,7 +97280,7 @@ function scaffoldTeamWorkspace(dir, name) {
97008
97280
  }
97009
97281
  function scaffoldMissionsWorkspace(dir, name) {
97010
97282
  const created = [];
97011
- const skillsDir = join50(dir, ".tmux-ide", "skills");
97283
+ const skillsDir = join51(dir, ".tmux-ide", "skills");
97012
97284
  created.push(...copyTemplateSkills(skillsDir));
97013
97285
  created.push(...scaffoldTeamWorkspace(dir, name));
97014
97286
  return created;
@@ -97040,11 +97312,11 @@ async function init({
97040
97312
  created = scaffoldMissionsWorkspace(dir, name2);
97041
97313
  } else if (isTeamTemplate(template)) {
97042
97314
  created = [
97043
- ...copyTemplateSkills(join50(dir, ".tmux-ide", "skills")),
97315
+ ...copyTemplateSkills(join51(dir, ".tmux-ide", "skills")),
97044
97316
  ...scaffoldTeamWorkspace(dir, name2)
97045
97317
  ];
97046
97318
  } else {
97047
- created = copyTemplateSkills(join50(dir, ".tmux-ide", "skills"));
97319
+ created = copyTemplateSkills(join51(dir, ".tmux-ide", "skills"));
97048
97320
  }
97049
97321
  if (json3) {
97050
97322
  console.log(JSON.stringify({ created: true, template, name: name2, paths: created }));
@@ -97086,7 +97358,7 @@ async function init({
97086
97358
  console.log("Edit it to configure your workspace, then run: tmux-ide");
97087
97359
  }
97088
97360
  }
97089
- const skillsDir = join50(dir, ".tmux-ide", "skills");
97361
+ const skillsDir = join51(dir, ".tmux-ide", "skills");
97090
97362
  if (!existsSync35(skillsDir)) {
97091
97363
  const created = copyTemplateSkills(skillsDir);
97092
97364
  if (created.length > 0 && !json3) {
@@ -97141,19 +97413,21 @@ var init_stop = __esm({
97141
97413
  // packages/daemon/src/open-browser.ts
97142
97414
  var open_browser_exports = {};
97143
97415
  __export(open_browser_exports, {
97416
+ NO_NATIVE_CONTROLS_NOTICE: () => NO_NATIVE_CONTROLS_NOTICE,
97144
97417
  createBrowserPane: () => createBrowserPane,
97418
+ nativeControlsNotice: () => nativeControlsNotice,
97145
97419
  openBrowser: () => openBrowser,
97146
97420
  resolveTerminalBrowser: () => resolveTerminalBrowser,
97147
97421
  validateBrowserOpen: () => validateBrowserOpen
97148
97422
  });
97149
- import { accessSync as accessSync10, constants as constants16, realpathSync as realpathSync27, statSync as statSync21 } from "node:fs";
97150
- import { delimiter as delimiter4, isAbsolute as isAbsolute28, join as join51, resolve as resolve37 } from "node:path";
97423
+ import { accessSync as accessSync10, constants as constants17, realpathSync as realpathSync27, statSync as statSync21 } from "node:fs";
97424
+ import { delimiter as delimiter4, isAbsolute as isAbsolute29, join as join52, resolve as resolve37 } from "node:path";
97151
97425
  import { homedir as homedir8 } from "node:os";
97152
97426
  function resolveTerminalBrowser(environment = process.env, platform2 = process.platform) {
97153
97427
  const configured = environment.TMUX_IDE_TERMINAL_BROWSER_BIN;
97154
97428
  const home = environment.HOME ?? homedir8();
97155
- const bundled = platform2 === "darwin" && isAbsolute28(home) ? [
97156
- join51(
97429
+ const bundled = platform2 === "darwin" && isAbsolute29(home) ? [
97430
+ join52(
97157
97431
  home,
97158
97432
  "Library",
97159
97433
  "Application Support",
@@ -97168,12 +97442,12 @@ function resolveTerminalBrowser(environment = process.env, platform2 = process.p
97168
97442
  ] : [];
97169
97443
  const candidates = configured ? [configured] : [
97170
97444
  ...bundled,
97171
- ...(environment.PATH ?? "").split(delimiter4).filter(isAbsolute28).map((dir) => join51(dir, "terminal-browser"))
97445
+ ...(environment.PATH ?? "").split(delimiter4).filter(isAbsolute29).map((dir) => join52(dir, "terminal-browser"))
97172
97446
  ];
97173
97447
  for (const candidate of candidates) {
97174
97448
  try {
97175
- if (!isAbsolute28(candidate)) continue;
97176
- accessSync10(candidate, constants16.X_OK);
97449
+ if (!isAbsolute29(candidate)) continue;
97450
+ accessSync10(candidate, constants17.X_OK);
97177
97451
  const path2 = realpathSync27(candidate);
97178
97452
  if (statSync21(path2).isFile()) return path2;
97179
97453
  } catch {
@@ -97218,6 +97492,26 @@ function validateBrowserOpen(url, options) {
97218
97492
  exitCode: 2
97219
97493
  });
97220
97494
  }
97495
+ function isBundledBrowser(executable) {
97496
+ return /\/[^/]+\.app\/Contents\/Resources\/browser\//u.test(executable);
97497
+ }
97498
+ async function nativeControlsNotice(run, pane, executable, wait = {}) {
97499
+ if (isBundledBrowser(executable)) return null;
97500
+ const sleep3 = wait.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
97501
+ const now = wait.now ?? Date.now;
97502
+ const deadline = now() + STANDALONE_WAIT_MS;
97503
+ for (; ; ) {
97504
+ let control;
97505
+ try {
97506
+ control = run(["display-message", "-p", "-t", pane, "#{@tmux_ide_browser_control}"]).trim();
97507
+ } catch {
97508
+ return null;
97509
+ }
97510
+ if (control) return null;
97511
+ if (now() >= deadline) return NO_NATIVE_CONTROLS_NOTICE;
97512
+ await sleep3(250);
97513
+ }
97514
+ }
97221
97515
  function createBrowserPane(run, executable, url, options, environment, cwd, namespace) {
97222
97516
  validateBrowserOpen(url, options);
97223
97517
  const inheritedPane = /^%\d+$/u.test(environment.TMUX_PANE ?? "") ? environment.TMUX_PANE : void 0;
@@ -97289,9 +97583,21 @@ function createBrowserPane(run, executable, url, options, environment, cwd, name
97289
97583
  });
97290
97584
  return { pane, session: createdSession, window: createdWindow, status: "created" };
97291
97585
  }
97292
- function openBrowser(url, options = {}) {
97586
+ async function openBrowser(url, options = {}, dependencies = {}) {
97293
97587
  validateBrowserOpen(url, options);
97294
- const executable = resolveTerminalBrowser();
97588
+ const executable = (dependencies.executable ?? resolveTerminalBrowser)();
97589
+ const { result: result2, run } = (dependencies.create ?? createPinnedBrowserPane)(
97590
+ executable,
97591
+ url,
97592
+ options
97593
+ );
97594
+ const message = await nativeControlsNotice(run, result2.pane, executable, dependencies.wait);
97595
+ if (message)
97596
+ (dependencies.notice ?? ((line) => process.stderr.write(`${line}
97597
+ `)))(message);
97598
+ return result2;
97599
+ }
97600
+ function createPinnedBrowserPane(executable, url, options) {
97295
97601
  try {
97296
97602
  const namespace = { ...runtimeNamespaceEnvironment(resolveRuntimeNamespace()) };
97297
97603
  const authority2 = resolveWorkspacePaneTmuxAuthority();
@@ -97299,7 +97605,16 @@ function openBrowser(url, options = {}) {
97299
97605
  const run = createPinnedWorkspaceTmuxRunner(authority2, {
97300
97606
  timeoutMs: 1e4
97301
97607
  });
97302
- return createBrowserPane(run, executable, url, options, process.env, process.cwd(), namespace);
97608
+ const result2 = createBrowserPane(
97609
+ run,
97610
+ executable,
97611
+ url,
97612
+ options,
97613
+ process.env,
97614
+ process.cwd(),
97615
+ namespace
97616
+ );
97617
+ return { result: result2, run };
97303
97618
  } catch (error) {
97304
97619
  if (error instanceof IdeError) throw error;
97305
97620
  throw new IdeError(
@@ -97311,12 +97626,15 @@ function openBrowser(url, options = {}) {
97311
97626
  );
97312
97627
  }
97313
97628
  }
97629
+ var NO_NATIVE_CONTROLS_NOTICE, STANDALONE_WAIT_MS;
97314
97630
  var init_open_browser = __esm({
97315
97631
  "packages/daemon/src/open-browser.ts"() {
97316
97632
  "use strict";
97317
97633
  init_errors2();
97318
97634
  init_runtime_namespace();
97319
97635
  init_workspace_pane_creation2();
97636
+ NO_NATIVE_CONTROLS_NOTICE = "This browser has no tmux-ide controls; install the native app for back/forward/reload.";
97637
+ STANDALONE_WAIT_MS = 2e3;
97320
97638
  }
97321
97639
  });
97322
97640
 
@@ -97475,7 +97793,7 @@ var init_daemon_service_manager = __esm({
97475
97793
 
97476
97794
  // packages/daemon/src/lib/daemon-service-plan.ts
97477
97795
  import { createHash as createHash34 } from "node:crypto";
97478
- import { isAbsolute as isAbsolute29, join as join52 } from "node:path";
97796
+ import { isAbsolute as isAbsolute30, join as join53 } from "node:path";
97479
97797
  function safeValue(value, name) {
97480
97798
  if (!value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
97481
97799
  throw new TypeError(`${name} must be nonempty and contain no control characters`);
@@ -97483,7 +97801,7 @@ function safeValue(value, name) {
97483
97801
  }
97484
97802
  function absolute(value, name) {
97485
97803
  safeValue(value, name);
97486
- if (!isAbsolute29(value)) throw new TypeError(`${name} must be an absolute path`);
97804
+ if (!isAbsolute30(value)) throw new TypeError(`${name} must be an absolute path`);
97487
97805
  return value;
97488
97806
  }
97489
97807
  function xml(value) {
@@ -97507,8 +97825,8 @@ function planDaemonService(options) {
97507
97825
  const executable = absolute(options.executable, "Stable CLI executable");
97508
97826
  const identity4 = createHash34("sha256").update(absolute(namespace.daemonInfoDir, "Daemon state directory")).digest("hex").slice(0, 24);
97509
97827
  const supervisionId = `tmux-ide.${identity4}`;
97510
- const stdoutPath = join52(namespace.daemonInfoDir, "service.stdout.log");
97511
- const stderrPath = join52(namespace.daemonInfoDir, "service.stderr.log");
97828
+ const stdoutPath = join53(namespace.daemonInfoDir, "service.stdout.log");
97829
+ const stderrPath = join53(namespace.daemonInfoDir, "service.stderr.log");
97512
97830
  const environment = {
97513
97831
  HOME: home,
97514
97832
  PATH: safeValue(options.path, "Service PATH"),
@@ -97525,7 +97843,7 @@ function planDaemonService(options) {
97525
97843
  const shared = {
97526
97844
  supervisionId,
97527
97845
  executable,
97528
- recordPath: join52(namespace.daemonInfoDir, "service.json"),
97846
+ recordPath: join53(namespace.daemonInfoDir, "service.json"),
97529
97847
  stdoutPath,
97530
97848
  stderrPath
97531
97849
  };
@@ -97536,7 +97854,7 @@ function planDaemonService(options) {
97536
97854
  ...shared,
97537
97855
  manager: "launchd",
97538
97856
  target: `gui/${options.uid}/${label4}`,
97539
- unitPath: join52(home, "Library", "LaunchAgents", `${label4}.plist`),
97857
+ unitPath: join53(home, "Library", "LaunchAgents", `${label4}.plist`),
97540
97858
  contents: `<?xml version="1.0" encoding="UTF-8"?>
97541
97859
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
97542
97860
  <plist version="1.0"><dict>
@@ -97555,14 +97873,14 @@ function planDaemonService(options) {
97555
97873
  `
97556
97874
  });
97557
97875
  }
97558
- const configHome = absolute(options.configHome ?? join52(home, ".config"), "Config directory");
97876
+ const configHome = absolute(options.configHome ?? join53(home, ".config"), "Config directory");
97559
97877
  const target = `${supervisionId}.service`;
97560
97878
  const workingDirectory = `${home.replace(/%/gu, "%%")}/.`;
97561
97879
  return Object.freeze({
97562
97880
  ...shared,
97563
97881
  manager: "systemd",
97564
97882
  target,
97565
- unitPath: join52(configHome, "systemd", "user", target),
97883
+ unitPath: join53(configHome, "systemd", "user", target),
97566
97884
  contents: `[Unit]
97567
97885
  Description=tmux-ide canonical user daemon
97568
97886
  StartLimitIntervalSec=60
@@ -97600,7 +97918,7 @@ __export(daemon_service_exports, {
97600
97918
  });
97601
97919
  import {
97602
97920
  accessSync as accessSync11,
97603
- constants as constants17,
97921
+ constants as constants18,
97604
97922
  openSync as openSync11,
97605
97923
  closeSync as closeSync11,
97606
97924
  fstatSync as fstatSync9,
@@ -97614,7 +97932,7 @@ import {
97614
97932
  linkSync as linkSync4
97615
97933
  } from "node:fs";
97616
97934
  import { randomUUID as randomUUID48 } from "node:crypto";
97617
- import { dirname as dirname41, join as join53 } from "node:path";
97935
+ import { dirname as dirname41, join as join54 } from "node:path";
97618
97936
  import { homedir as homedir9 } from "node:os";
97619
97937
  import { z as z131 } from "zod";
97620
97938
  function defaults() {
@@ -97634,7 +97952,7 @@ function defaults() {
97634
97952
  reserve: reserveCanonicalDaemonSupervision,
97635
97953
  release: releaseCanonicalDaemonSupervision,
97636
97954
  probe: probeCanonicalDaemonIdentity,
97637
- recordPath: join53(dirname41(getCanonicalDaemonInfoPath()), "service.json"),
97955
+ recordPath: join54(dirname41(getCanonicalDaemonInfoPath()), "service.json"),
97638
97956
  waitMs: 3e4
97639
97957
  };
97640
97958
  }
@@ -97644,7 +97962,7 @@ function refused(message) {
97644
97962
  function ownedFile(path2) {
97645
97963
  let descriptor;
97646
97964
  try {
97647
- descriptor = openSync11(path2, constants17.O_RDONLY | constants17.O_NOFOLLOW | constants17.O_NONBLOCK);
97965
+ descriptor = openSync11(path2, constants18.O_RDONLY | constants18.O_NOFOLLOW | constants18.O_NONBLOCK);
97648
97966
  const stat3 = fstatSync9(descriptor);
97649
97967
  if (!stat3.isFile() || stat3.uid !== process.getuid?.() || (stat3.mode & 18) !== 0 || stat3.size > 128 * 1024)
97650
97968
  refused(`Service file is not a bounded, user-owned regular file: ${path2}`);
@@ -97737,7 +98055,7 @@ async function manageDaemonService(action, executable, deps2 = defaults()) {
97737
98055
  if (record2 && !boundTo(deps2.inspect(), plan.supervisionId) && !(record2.removing && action === "remove" && deps2.inspect().status === "missing"))
97738
98056
  refused("Service reservation is missing or belongs to another owner");
97739
98057
  if (!record2) {
97740
- accessSync11(plan.executable, constants17.X_OK);
98058
+ accessSync11(plan.executable, constants18.X_OK);
97741
98059
  deps2.reserve(plan.supervisionId);
97742
98060
  }
97743
98061
  const lock = `${plan.recordPath}.lock`;
@@ -98353,23 +98671,23 @@ __export(skill_sync_exports, {
98353
98671
  versionMarker: () => versionMarker
98354
98672
  });
98355
98673
  import { existsSync as existsSync36, mkdirSync as mkdirSync31, readFileSync as readFileSync37, writeFileSync as writeFileSync29 } from "node:fs";
98356
- import { dirname as dirname42, join as join54 } from "node:path";
98674
+ import { dirname as dirname42, join as join55 } from "node:path";
98357
98675
  import { fileURLToPath as fileURLToPath12 } from "node:url";
98358
98676
  function claudeDir() {
98359
98677
  return resolveRuntimeNamespace().claudeDir;
98360
98678
  }
98361
98679
  function skillTargetDir() {
98362
- return runtimeOwnedPath(join54(claudeDir(), "skills", "tmux-ide"));
98680
+ return runtimeOwnedPath(join55(claudeDir(), "skills", "tmux-ide"));
98363
98681
  }
98364
98682
  function skillTargetFile() {
98365
- return join54(skillTargetDir(), "SKILL.md");
98683
+ return join55(skillTargetDir(), "SKILL.md");
98366
98684
  }
98367
98685
  function defaultSkillSource() {
98368
98686
  const here = dirname42(fileURLToPath12(import.meta.url));
98369
98687
  const candidates = [
98370
- join54(here, "../skill/SKILL.md"),
98688
+ join55(here, "../skill/SKILL.md"),
98371
98689
  // bundled bin/cli.js → repo root
98372
- join54(here, "../../../../skill/SKILL.md")
98690
+ join55(here, "../../../../skill/SKILL.md")
98373
98691
  // dev src/lib → repo root
98374
98692
  ];
98375
98693
  return candidates.find((c) => existsSync36(c)) ?? candidates[0];
@@ -98386,7 +98704,7 @@ function rewriteVersionMarker(content, version) {
98386
98704
  return content.replace(VERSION_MARKER_RE, versionMarker(version));
98387
98705
  }
98388
98706
  function installedSkillVersion(dir = skillTargetDir()) {
98389
- const file = join54(dir, "SKILL.md");
98707
+ const file = join55(dir, "SKILL.md");
98390
98708
  if (!existsSync36(file)) return null;
98391
98709
  try {
98392
98710
  return parseSkillVersion(readFileSync37(file, "utf-8"));
@@ -98402,7 +98720,7 @@ function syncSkill({
98402
98720
  throw new Error("Automatic skill sync is disabled in development instances");
98403
98721
  const rendered = rewriteVersionMarker(readFileSync37(source, "utf-8"), version);
98404
98722
  const dir = skillTargetDir();
98405
- const target = join54(dir, "SKILL.md");
98723
+ const target = join55(dir, "SKILL.md");
98406
98724
  const existing = existsSync36(target) ? readFileSync37(target, "utf-8") : null;
98407
98725
  if (existing === rendered) {
98408
98726
  return { action: "unchanged", path: target, to: version };
@@ -98435,7 +98753,7 @@ __export(doctor_exports, {
98435
98753
  workspaceConfigRow: () => workspaceConfigRow
98436
98754
  });
98437
98755
  import { execSync as execSync3, execFileSync as execFileSync22 } from "node:child_process";
98438
- import { accessSync as accessSync12, constants as constants18, existsSync as existsSync37 } from "node:fs";
98756
+ import { accessSync as accessSync12, constants as constants19, existsSync as existsSync37 } from "node:fs";
98439
98757
  import { resolve as resolve39, dirname as dirname43 } from "node:path";
98440
98758
  import { fileURLToPath as fileURLToPath13 } from "node:url";
98441
98759
  function agentIntegrationRows(agents) {
@@ -98650,7 +98968,7 @@ async function doctor({
98650
98968
  }
98651
98969
  let writable = false;
98652
98970
  try {
98653
- accessSync12(probe, constants18.W_OK);
98971
+ accessSync12(probe, constants19.W_OK);
98654
98972
  writable = true;
98655
98973
  } catch {
98656
98974
  }
@@ -101212,11 +101530,11 @@ __export(pane_team_exports, {
101212
101530
  assignPaneTeam: () => assignPaneTeam
101213
101531
  });
101214
101532
  import { execFileSync as execFileSync25 } from "node:child_process";
101215
- import { isAbsolute as isAbsolute30 } from "node:path";
101533
+ import { isAbsolute as isAbsolute31 } from "node:path";
101216
101534
  function assignPaneTeam(options, run = (args) => execFileSync25("tmux", args, { encoding: "utf8", timeout: 5e3 })) {
101217
101535
  if (!/^%\d+$/u.test(options.paneId)) throw new Error("Use an exact pane ID such as %3");
101218
101536
  if (options.socketPath && options.socketName) throw new Error("Choose one tmux socket selector");
101219
- if (options.socketPath && !isAbsolute30(options.socketPath))
101537
+ if (options.socketPath && !isAbsolute31(options.socketPath))
101220
101538
  throw new Error("Socket path must be absolute");
101221
101539
  if (options.socketName && !/^[a-zA-Z0-9_.-]+$/u.test(options.socketName))
101222
101540
  throw new Error("Invalid socket name");
@@ -101453,9 +101771,9 @@ import {
101453
101771
  unlinkSync as unlinkSync10,
101454
101772
  writeFileSync as writeFileSync30,
101455
101773
  accessSync as accessSync13,
101456
- constants as constants19
101774
+ constants as constants20
101457
101775
  } from "node:fs";
101458
- import { basename as basename21, delimiter as delimiter5, dirname as dirname45, join as join55 } from "node:path";
101776
+ import { basename as basename21, delimiter as delimiter5, dirname as dirname45, join as join56 } from "node:path";
101459
101777
  function isOptOut(value) {
101460
101778
  if (value === false || value === 0) return true;
101461
101779
  return typeof value === "string" && /^\s*(0|false|off|no)\s*$/iu.test(value);
@@ -101503,8 +101821,8 @@ function agentTeamsPaths() {
101503
101821
  function claudeOnPath2(path2 = process.env.PATH ?? "") {
101504
101822
  return path2.split(delimiter5).filter(Boolean).some((dir) => {
101505
101823
  try {
101506
- accessSync13(join55(dir, "claude"), constants19.X_OK);
101507
- return statSync23(join55(dir, "claude")).isFile();
101824
+ accessSync13(join56(dir, "claude"), constants20.X_OK);
101825
+ return statSync23(join56(dir, "claude")).isFile();
101508
101826
  } catch {
101509
101827
  return false;
101510
101828
  }
@@ -101529,7 +101847,7 @@ function existingBackup(settingsPath) {
101529
101847
  const prefix = `${basename21(settingsPath)}${BACKUP_INFIX}`;
101530
101848
  try {
101531
101849
  const found = readdirSync11(dir).find((name) => name.startsWith(prefix));
101532
- return found ? join55(dir, found) : null;
101850
+ return found ? join56(dir, found) : null;
101533
101851
  } catch {
101534
101852
  return null;
101535
101853
  }
@@ -101545,7 +101863,7 @@ function writeSettings(path2, settings, exists) {
101545
101863
  copyFileSync3(path2, backup);
101546
101864
  }
101547
101865
  }
101548
- const temporary = join55(dirname45(path2), `.${basename21(path2)}.tmux-ide-${process.pid}.tmp`);
101866
+ const temporary = join56(dirname45(path2), `.${basename21(path2)}.tmux-ide-${process.pid}.tmp`);
101549
101867
  try {
101550
101868
  writeFileSync30(temporary, `${JSON.stringify(settings, null, 2)}
101551
101869
  `, { mode: 384 });
@@ -101686,7 +102004,7 @@ __export(worktree_exports, {
101686
102004
  worktreeSessionName: () => worktreeSessionName
101687
102005
  });
101688
102006
  import { execFileSync as execFileSync26 } from "node:child_process";
101689
- import { basename as basename22, dirname as dirname46, isAbsolute as isAbsolute31, join as join56, resolve as resolve43 } from "node:path";
102007
+ import { basename as basename22, dirname as dirname46, isAbsolute as isAbsolute32, join as join57, resolve as resolve43 } from "node:path";
101690
102008
  function sanitizeForTmux(part) {
101691
102009
  return part.replace(/[.:/\s]+/g, "-");
101692
102010
  }
@@ -101695,11 +102013,11 @@ function worktreeSessionName(project, branch) {
101695
102013
  }
101696
102014
  function defaultWorktreeBaseDir(repoDir) {
101697
102015
  const abs = resolve43(repoDir);
101698
- return join56(dirname46(abs), `${basename22(abs)}-worktrees`);
102016
+ return join57(dirname46(abs), `${basename22(abs)}-worktrees`);
101699
102017
  }
101700
102018
  function worktreePath(repoDir, branch, configuredDir) {
101701
- const base2 = configuredDir && configuredDir.length > 0 ? isAbsolute31(configuredDir) ? configuredDir : resolve43(repoDir, configuredDir) : defaultWorktreeBaseDir(repoDir);
101702
- return join56(base2, branch);
102019
+ const base2 = configuredDir && configuredDir.length > 0 ? isAbsolute32(configuredDir) ? configuredDir : resolve43(repoDir, configuredDir) : defaultWorktreeBaseDir(repoDir);
102020
+ return join57(base2, branch);
101703
102021
  }
101704
102022
  function parseWorktreeList(porcelain) {
101705
102023
  const entries = [];
@@ -101834,7 +102152,7 @@ var init_worktree = __esm({
101834
102152
 
101835
102153
  // packages/daemon/src/lib/managed-installer-update.ts
101836
102154
  import { mkdtempSync, readFileSync as readFileSync40, realpathSync as realpathSync29, rmSync as rmSync7 } from "node:fs";
101837
- import { dirname as dirname47, join as join57 } from "node:path";
102155
+ import { dirname as dirname47, join as join58 } from "node:path";
101838
102156
  import { tmpdir } from "node:os";
101839
102157
  import { execFileSync as execFileSync27 } from "node:child_process";
101840
102158
  function managedInstallerPrefix(cliPath) {
@@ -101843,16 +102161,16 @@ function managedInstallerPrefix(cliPath) {
101843
102161
  );
101844
102162
  if (!match) return null;
101845
102163
  const prefix = match[1] || "/";
101846
- const root = join57(prefix, "share/tmux-ide");
101847
- const release = join57(root, "releases", match[2]);
102164
+ const root = join58(prefix, "share/tmux-ide");
102165
+ const release = join58(root, "releases", match[2]);
101848
102166
  try {
101849
- if (realpathSync29(join57(root, "current")) !== release) return null;
102167
+ if (realpathSync29(join58(root, "current")) !== release) return null;
101850
102168
  if (realpathSync29(release) !== release) return null;
101851
- if (readFileSync40(join57(root, "installer-v1"), "utf8") !== "1\n") return null;
101852
- if (readFileSync40(join57(release, ".installer-release-v1"), "utf8") !== "1\n") return null;
101853
- if (!readFileSync40(join57(prefix, "bin/tmux-ide"), "utf8").split("\n").includes("# tmux-ide universal installer v1"))
102169
+ if (readFileSync40(join58(root, "installer-v1"), "utf8") !== "1\n") return null;
102170
+ if (readFileSync40(join58(release, ".installer-release-v1"), "utf8") !== "1\n") return null;
102171
+ if (!readFileSync40(join58(prefix, "bin/tmux-ide"), "utf8").split("\n").includes("# tmux-ide universal installer v1"))
101854
102172
  return null;
101855
- if (JSON.parse(readFileSync40(join57(dirname47(cliPath), "package.json"), "utf8")).name !== "tmux-ide")
102173
+ if (JSON.parse(readFileSync40(join58(dirname47(cliPath), "package.json"), "utf8")).name !== "tmux-ide")
101856
102174
  return null;
101857
102175
  return prefix;
101858
102176
  } catch {
@@ -101860,8 +102178,8 @@ function managedInstallerPrefix(cliPath) {
101860
102178
  }
101861
102179
  }
101862
102180
  function runManagedInstallerUpdate(prefix, channel, json3, execute2 = execFileSync27) {
101863
- const temporary = mkdtempSync(join57(tmpdir(), "tmux-ide-update-"));
101864
- const script = join57(temporary, "install.sh");
102181
+ const temporary = mkdtempSync(join58(tmpdir(), "tmux-ide-update-"));
102182
+ const script = join58(temporary, "install.sh");
101865
102183
  try {
101866
102184
  execute2(
101867
102185
  "curl",
@@ -101904,7 +102222,7 @@ var init_managed_installer_update = __esm({
101904
102222
 
101905
102223
  // packages/daemon/src/lib/install-origin.ts
101906
102224
  import { existsSync as existsSync39, readFileSync as readFileSync41, realpathSync as realpathSync30 } from "node:fs";
101907
- import { dirname as dirname48, join as join58 } from "node:path";
102225
+ import { dirname as dirname48, join as join59 } from "node:path";
101908
102226
  import { fileURLToPath as fileURLToPath14 } from "node:url";
101909
102227
  function detectPackageManager(path2) {
101910
102228
  if (/\/share\/tmux-ide\/releases\/install-[^/]+\/npm\/lib\/node_modules\/tmux-ide\/bin$/.test(path2))
@@ -101921,9 +102239,9 @@ function findGitCheckoutRoot(startDir) {
101921
102239
  let dir = startDir;
101922
102240
  for (; ; ) {
101923
102241
  if (dir.endsWith("/node_modules")) return null;
101924
- if (existsSync39(join58(dir, ".git"))) {
102242
+ if (existsSync39(join59(dir, ".git"))) {
101925
102243
  try {
101926
- if (JSON.parse(readFileSync41(join58(dir, "package.json"), "utf8")).name === "tmux-ide")
102244
+ if (JSON.parse(readFileSync41(join59(dir, "package.json"), "utf8")).name === "tmux-ide")
101927
102245
  return dir;
101928
102246
  } catch {
101929
102247
  }
@@ -101944,7 +102262,7 @@ function installOrigin(cliDir = dirname48(fileURLToPath14(import.meta.url))) {
101944
102262
  let detected2 = detectPackageManager(path2);
101945
102263
  if (detected2 === "npm" || detected2 === "pnpm" || detected2 === "bun") {
101946
102264
  try {
101947
- if (JSON.parse(readFileSync41(join58(path2, "../package.json"), "utf8")).name !== "tmux-ide")
102265
+ if (JSON.parse(readFileSync41(join59(path2, "../package.json"), "utf8")).name !== "tmux-ide")
101948
102266
  detected2 = "unknown";
101949
102267
  } catch {
101950
102268
  detected2 = "unknown";
@@ -101970,7 +102288,7 @@ __export(update_exports, {
101970
102288
  runUpdate: () => runUpdate
101971
102289
  });
101972
102290
  import { realpathSync as realpathSync31 } from "node:fs";
101973
- import { dirname as dirname49, join as join59 } from "node:path";
102291
+ import { dirname as dirname49, join as join60 } from "node:path";
101974
102292
  import { execFileSync as execFileSync28 } from "node:child_process";
101975
102293
  function planUpdate(input) {
101976
102294
  const current = input.currentVersion ?? getCurrentVersion();
@@ -102060,7 +102378,7 @@ function runUpdate({ cliDir, dryRun, json: json3 = false }, dependencies = {}) {
102060
102378
  plan.executable === "bun" ? ["pm", "bin", "-g"] : ["root", "-g"]
102061
102379
  ).trim();
102062
102380
  if (!root.startsWith("/")) throw new Error("invalid manager path");
102063
- const target = plan.executable === "bun" ? dirname49(realpathSync31(join59(root, "tmux-ide"))) : realpathSync31(join59(root, "tmux-ide", "bin"));
102381
+ const target = plan.executable === "bun" ? dirname49(realpathSync31(join60(root, "tmux-ide"))) : realpathSync31(join60(root, "tmux-ide", "bin"));
102064
102382
  if (target !== source.path) throw new Error("different installation");
102065
102383
  } catch {
102066
102384
  plan = {
@@ -102122,9 +102440,9 @@ __export(pane_widget_exports, {
102122
102440
  paneWidgetId: () => paneWidgetId,
102123
102441
  paneWidgetIdForFile: () => paneWidgetIdForFile
102124
102442
  });
102125
- import { basename as basename23, extname } from "node:path";
102443
+ import { basename as basename23, extname as extname2 } from "node:path";
102126
102444
  function imageMediaTypeFor(fileName) {
102127
- return IMAGE_MEDIA_BY_EXTENSION.get(extname(fileName).toLowerCase()) ?? null;
102445
+ return IMAGE_MEDIA_BY_EXTENSION.get(extname2(fileName).toLowerCase()) ?? null;
102128
102446
  }
102129
102447
  function buildMarkdownAnnouncement(text2, title) {
102130
102448
  if (text2.trim().length === 0) {
@@ -102190,7 +102508,7 @@ function paneWidgetId(value) {
102190
102508
  );
102191
102509
  }
102192
102510
  function paneWidgetIdForFile(fileName) {
102193
- const extension = extname(fileName).toLowerCase();
102511
+ const extension = extname2(fileName).toLowerCase();
102194
102512
  if ([".md", ".markdown"].includes(extension)) return "markdown";
102195
102513
  if (IMAGE_MEDIA_BY_EXTENSION.has(extension)) return "image";
102196
102514
  if (extension === ".json") return "card";
@@ -102337,7 +102655,7 @@ var init_server3 = __esm({
102337
102655
  init_src();
102338
102656
  init_tmux_client_execution();
102339
102657
  import { parseArgs as parseArgs2 } from "node:util";
102340
- import { resolve as resolve44, dirname as dirname50, join as join60 } from "node:path";
102658
+ import { resolve as resolve44, dirname as dirname50, join as join61 } from "node:path";
102341
102659
  import { execFileSync as execFileSync29 } from "node:child_process";
102342
102660
  import { appendFileSync as appendFileSync2, existsSync as existsSync40, mkdirSync as mkdirSync33, writeFileSync as writeFileSync31 } from "node:fs";
102343
102661
  import { fileURLToPath as fileURLToPath15 } from "node:url";
@@ -102678,9 +102996,9 @@ Install bun (https://bun.sh) \u2014 the TUI surfaces run on it. Sources ship wit
102678
102996
  let automaticDiagnosticLog;
102679
102997
  if (surface === "app" && !process.env.TMUX_IDE_TUI_PERF_LOG && !process.env.TMUX_IDE_TUI_LOG) {
102680
102998
  try {
102681
- const logDirectory = join60(stateHome(), "logs");
102999
+ const logDirectory = join61(stateHome(), "logs");
102682
103000
  mkdirSync33(logDirectory, { recursive: true, mode: 448 });
102683
- automaticDiagnosticLog = join60(logDirectory, "tui-latest.jsonl");
103001
+ automaticDiagnosticLog = join61(logDirectory, "tui-latest.jsonl");
102684
103002
  writeFileSync31(
102685
103003
  automaticDiagnosticLog,
102686
103004
  `${JSON.stringify({
@@ -103052,7 +103370,7 @@ try {
103052
103370
  exitCode: 2
103053
103371
  });
103054
103372
  const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_open_browser(), open_browser_exports));
103055
- const result2 = openBrowser2(positionals[1], {
103373
+ const result2 = await openBrowser2(positionals[1], {
103056
103374
  session: values.session,
103057
103375
  pane: values.pane,
103058
103376
  split: values.split,