aemeathcli 1.0.0

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 (102) hide show
  1. package/README.md +607 -0
  2. package/dist/App-P4MYD4QY.js +2719 -0
  3. package/dist/App-P4MYD4QY.js.map +1 -0
  4. package/dist/api-key-fallback-YQQBOQIL.js +11 -0
  5. package/dist/api-key-fallback-YQQBOQIL.js.map +1 -0
  6. package/dist/chunk-4IJD72YB.js +184 -0
  7. package/dist/chunk-4IJD72YB.js.map +1 -0
  8. package/dist/chunk-6PDJ45T4.js +325 -0
  9. package/dist/chunk-6PDJ45T4.js.map +1 -0
  10. package/dist/chunk-CARHU3DO.js +562 -0
  11. package/dist/chunk-CARHU3DO.js.map +1 -0
  12. package/dist/chunk-CGEV3ARR.js +80 -0
  13. package/dist/chunk-CGEV3ARR.js.map +1 -0
  14. package/dist/chunk-CS5X3BWX.js +27 -0
  15. package/dist/chunk-CS5X3BWX.js.map +1 -0
  16. package/dist/chunk-CYQNBB25.js +44 -0
  17. package/dist/chunk-CYQNBB25.js.map +1 -0
  18. package/dist/chunk-DAHGLHNR.js +657 -0
  19. package/dist/chunk-DAHGLHNR.js.map +1 -0
  20. package/dist/chunk-H66O5Z2V.js +305 -0
  21. package/dist/chunk-H66O5Z2V.js.map +1 -0
  22. package/dist/chunk-HCIHOHLX.js +322 -0
  23. package/dist/chunk-HCIHOHLX.js.map +1 -0
  24. package/dist/chunk-HMJRPNPZ.js +1031 -0
  25. package/dist/chunk-HMJRPNPZ.js.map +1 -0
  26. package/dist/chunk-I5PZ4JTS.js +119 -0
  27. package/dist/chunk-I5PZ4JTS.js.map +1 -0
  28. package/dist/chunk-IYW62KKR.js +255 -0
  29. package/dist/chunk-IYW62KKR.js.map +1 -0
  30. package/dist/chunk-JAXXTYID.js +51 -0
  31. package/dist/chunk-JAXXTYID.js.map +1 -0
  32. package/dist/chunk-LSOYPSAT.js +183 -0
  33. package/dist/chunk-LSOYPSAT.js.map +1 -0
  34. package/dist/chunk-MFBHNWGV.js +416 -0
  35. package/dist/chunk-MFBHNWGV.js.map +1 -0
  36. package/dist/chunk-MXZSI3AY.js +311 -0
  37. package/dist/chunk-MXZSI3AY.js.map +1 -0
  38. package/dist/chunk-NBR3GHMT.js +72 -0
  39. package/dist/chunk-NBR3GHMT.js.map +1 -0
  40. package/dist/chunk-O3ZF22SW.js +246 -0
  41. package/dist/chunk-O3ZF22SW.js.map +1 -0
  42. package/dist/chunk-SUSJPZU2.js +181 -0
  43. package/dist/chunk-SUSJPZU2.js.map +1 -0
  44. package/dist/chunk-TEVZS4FA.js +310 -0
  45. package/dist/chunk-TEVZS4FA.js.map +1 -0
  46. package/dist/chunk-UY2SYSEZ.js +211 -0
  47. package/dist/chunk-UY2SYSEZ.js.map +1 -0
  48. package/dist/chunk-WAHVZH7V.js +260 -0
  49. package/dist/chunk-WAHVZH7V.js.map +1 -0
  50. package/dist/chunk-WPP3PEDE.js +234 -0
  51. package/dist/chunk-WPP3PEDE.js.map +1 -0
  52. package/dist/chunk-Y5XVD2CD.js +1610 -0
  53. package/dist/chunk-Y5XVD2CD.js.map +1 -0
  54. package/dist/chunk-YL5XFHR3.js +56 -0
  55. package/dist/chunk-YL5XFHR3.js.map +1 -0
  56. package/dist/chunk-ZGOHARPV.js +122 -0
  57. package/dist/chunk-ZGOHARPV.js.map +1 -0
  58. package/dist/claude-adapter-QMLFMSP3.js +6 -0
  59. package/dist/claude-adapter-QMLFMSP3.js.map +1 -0
  60. package/dist/claude-login-5WELXPKT.js +324 -0
  61. package/dist/claude-login-5WELXPKT.js.map +1 -0
  62. package/dist/cli.d.ts +1 -0
  63. package/dist/cli.js +703 -0
  64. package/dist/cli.js.map +1 -0
  65. package/dist/codex-login-7HHLJHBF.js +164 -0
  66. package/dist/codex-login-7HHLJHBF.js.map +1 -0
  67. package/dist/config-store-W6FBCQAQ.js +6 -0
  68. package/dist/config-store-W6FBCQAQ.js.map +1 -0
  69. package/dist/executor-6RIKIGXK.js +4 -0
  70. package/dist/executor-6RIKIGXK.js.map +1 -0
  71. package/dist/gemini-adapter-6JIHZ7WI.js +6 -0
  72. package/dist/gemini-adapter-6JIHZ7WI.js.map +1 -0
  73. package/dist/gemini-login-ZZLYC3J6.js +346 -0
  74. package/dist/gemini-login-ZZLYC3J6.js.map +1 -0
  75. package/dist/index.d.ts +2210 -0
  76. package/dist/index.js +1419 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/kimi-adapter-JN4HFFHU.js +6 -0
  79. package/dist/kimi-adapter-JN4HFFHU.js.map +1 -0
  80. package/dist/kimi-login-CZPS63NK.js +149 -0
  81. package/dist/kimi-login-CZPS63NK.js.map +1 -0
  82. package/dist/native-cli-adapters-OLW3XX57.js +6 -0
  83. package/dist/native-cli-adapters-OLW3XX57.js.map +1 -0
  84. package/dist/ollama-adapter-OJQ3FKWK.js +6 -0
  85. package/dist/ollama-adapter-OJQ3FKWK.js.map +1 -0
  86. package/dist/openai-adapter-XU46EN7B.js +6 -0
  87. package/dist/openai-adapter-XU46EN7B.js.map +1 -0
  88. package/dist/registry-4KD24ZC3.js +6 -0
  89. package/dist/registry-4KD24ZC3.js.map +1 -0
  90. package/dist/registry-H7B3AHPQ.js +5 -0
  91. package/dist/registry-H7B3AHPQ.js.map +1 -0
  92. package/dist/server-manager-PTGBHCLS.js +5 -0
  93. package/dist/server-manager-PTGBHCLS.js.map +1 -0
  94. package/dist/session-manager-ECEEACGY.js +12 -0
  95. package/dist/session-manager-ECEEACGY.js.map +1 -0
  96. package/dist/team-manager-HC4XGCFY.js +11 -0
  97. package/dist/team-manager-HC4XGCFY.js.map +1 -0
  98. package/dist/tmux-manager-GPYZ3WQH.js +6 -0
  99. package/dist/tmux-manager-GPYZ3WQH.js.map +1 -0
  100. package/dist/tools-TSMXMHIF.js +6 -0
  101. package/dist/tools-TSMXMHIF.js.map +1 -0
  102. package/package.json +89 -0
@@ -0,0 +1,562 @@
1
+ import { getEventBus } from './chunk-YL5XFHR3.js';
2
+ import { AgentSpawnError } from './chunk-ZGOHARPV.js';
3
+ import { logger } from './chunk-JAXXTYID.js';
4
+ import { execa } from 'execa';
5
+
6
+ // src/panes/layout-engine.ts
7
+ var MIN_PANE_WIDTH = 40;
8
+ var MIN_PANE_HEIGHT = 10;
9
+ var DEFAULT_TERMINAL_WIDTH = 120;
10
+ var DEFAULT_TERMINAL_HEIGHT = 40;
11
+ var MAX_GRID_COLUMNS = 3;
12
+ var LayoutEngine = class {
13
+ terminalSize;
14
+ constructor(terminalSize) {
15
+ this.terminalSize = {
16
+ columns: terminalSize?.columns ?? this.detectTerminalWidth(),
17
+ rows: terminalSize?.rows ?? this.detectTerminalHeight()
18
+ };
19
+ logger.debug(
20
+ { terminalSize: this.terminalSize },
21
+ "LayoutEngine initialized"
22
+ );
23
+ }
24
+ /**
25
+ * Compute the full layout for a set of pane configs.
26
+ */
27
+ computeLayout(config) {
28
+ const paneCount = config.panes.length;
29
+ if (paneCount === 0) {
30
+ return {
31
+ panes: [],
32
+ rows: 0,
33
+ cols: 0,
34
+ terminalWidth: this.terminalSize.columns,
35
+ terminalHeight: this.terminalSize.rows
36
+ };
37
+ }
38
+ const effectiveCount = Math.max(
39
+ 1,
40
+ Math.min(paneCount, config.maxPanes, this.getMaxPanes())
41
+ );
42
+ const layout = config.layout === "auto" ? this.resolveAutoLayout(effectiveCount) : config.layout;
43
+ const geometries = this.computeGeometries(
44
+ config.panes.slice(0, effectiveCount),
45
+ layout,
46
+ effectiveCount
47
+ );
48
+ const { gridRows, gridCols } = this.getGridDimensions(effectiveCount, layout);
49
+ logger.info(
50
+ { layout, paneCount: effectiveCount, gridRows, gridCols },
51
+ "Layout computed"
52
+ );
53
+ return {
54
+ panes: geometries,
55
+ rows: gridRows,
56
+ cols: gridCols,
57
+ terminalWidth: this.terminalSize.columns,
58
+ terminalHeight: this.terminalSize.rows
59
+ };
60
+ }
61
+ /**
62
+ * Determine the best auto-layout for a given pane count (PRD section 9.2).
63
+ */
64
+ resolveAutoLayout(paneCount) {
65
+ if (paneCount <= 1) return "horizontal";
66
+ if (paneCount === 2) return "horizontal";
67
+ if (paneCount <= 4) return "grid";
68
+ return "grid";
69
+ }
70
+ /**
71
+ * Get the maximum number of panes the terminal can support.
72
+ */
73
+ getMaxPanes() {
74
+ const maxByWidth = Math.floor(this.terminalSize.columns / MIN_PANE_WIDTH);
75
+ const maxByHeight = Math.floor(this.terminalSize.rows / MIN_PANE_HEIGHT);
76
+ return Math.max(1, maxByWidth * maxByHeight);
77
+ }
78
+ /**
79
+ * Check if the terminal is large enough for the requested pane count.
80
+ */
81
+ canFitPanes(paneCount) {
82
+ return paneCount <= this.getMaxPanes();
83
+ }
84
+ /**
85
+ * Get the grid dimensions for a given pane count and layout type.
86
+ */
87
+ getGridDimensions(paneCount, layout) {
88
+ switch (layout) {
89
+ case "horizontal":
90
+ return { gridRows: 1, gridCols: paneCount };
91
+ case "vertical":
92
+ return { gridRows: paneCount, gridCols: 1 };
93
+ case "grid":
94
+ return this.computeGridSize(paneCount);
95
+ }
96
+ }
97
+ /**
98
+ * Compute grid rows and columns for a given pane count.
99
+ * PRD section 9.2:
100
+ * 2 agents → horizontal split (50/50) → 1x2
101
+ * 3 agents → 1 top + 2 bottom → 2 rows
102
+ * 4 agents → 2x2 grid
103
+ * 5+ agents → leader top + grid bottom
104
+ */
105
+ computeGridSize(paneCount) {
106
+ if (paneCount <= 1) return { gridRows: 1, gridCols: 1 };
107
+ if (paneCount === 2) return { gridRows: 1, gridCols: 2 };
108
+ if (paneCount <= 4) return { gridRows: 2, gridCols: 2 };
109
+ const bottomPaneCount = paneCount - 1;
110
+ const gridCols = Math.min(bottomPaneCount, MAX_GRID_COLUMNS);
111
+ const gridRows = 1 + Math.ceil(bottomPaneCount / gridCols);
112
+ return { gridRows, gridCols };
113
+ }
114
+ /**
115
+ * Compute per-pane geometries based on layout type.
116
+ */
117
+ computeGeometries(panes, layout, paneCount) {
118
+ switch (layout) {
119
+ case "horizontal":
120
+ return this.computeHorizontalLayout(panes);
121
+ case "vertical":
122
+ return this.computeVerticalLayout(panes);
123
+ case "grid":
124
+ return this.computeGridLayout(panes, paneCount);
125
+ }
126
+ }
127
+ /**
128
+ * Horizontal split: all panes side by side.
129
+ */
130
+ computeHorizontalLayout(panes) {
131
+ const widthPercent = Math.floor(100 / panes.length);
132
+ return panes.map((pane, index) => ({
133
+ paneId: pane.paneId,
134
+ row: 0,
135
+ col: index,
136
+ widthPercent: index === panes.length - 1 ? 100 - widthPercent * (panes.length - 1) : widthPercent,
137
+ heightPercent: 100,
138
+ splitDirection: index === 0 ? "none" : "horizontal"
139
+ }));
140
+ }
141
+ /**
142
+ * Vertical split: all panes stacked.
143
+ */
144
+ computeVerticalLayout(panes) {
145
+ const heightPercent = Math.floor(100 / panes.length);
146
+ return panes.map((pane, index) => ({
147
+ paneId: pane.paneId,
148
+ row: index,
149
+ col: 0,
150
+ widthPercent: 100,
151
+ heightPercent: index === panes.length - 1 ? 100 - heightPercent * (panes.length - 1) : heightPercent,
152
+ splitDirection: index === 0 ? "none" : "vertical"
153
+ }));
154
+ }
155
+ /**
156
+ * Grid layout per PRD section 9.2 rules:
157
+ * 3 agents → leader spans top, 2 on bottom
158
+ * 4 agents → 2x2 even grid
159
+ * 5+ agents → leader spans top, rest in grid below
160
+ */
161
+ computeGridLayout(panes, paneCount) {
162
+ const geometries = [];
163
+ if (paneCount <= 2) {
164
+ return this.computeHorizontalLayout(panes);
165
+ }
166
+ if (paneCount === 3) {
167
+ const firstPane2 = panes[0];
168
+ if (firstPane2) {
169
+ geometries.push({
170
+ paneId: firstPane2.paneId,
171
+ row: 0,
172
+ col: 0,
173
+ widthPercent: 100,
174
+ heightPercent: 50,
175
+ splitDirection: "none"
176
+ });
177
+ }
178
+ const bottomPanes2 = panes.slice(1);
179
+ for (let i = 0; i < bottomPanes2.length; i++) {
180
+ const pane = bottomPanes2[i];
181
+ if (pane) {
182
+ geometries.push({
183
+ paneId: pane.paneId,
184
+ row: 1,
185
+ col: i,
186
+ widthPercent: 50,
187
+ heightPercent: 50,
188
+ splitDirection: i === 0 ? "vertical" : "horizontal"
189
+ });
190
+ }
191
+ }
192
+ return geometries;
193
+ }
194
+ if (paneCount === 4) {
195
+ const { gridCols } = this.computeGridSize(4);
196
+ for (let i = 0; i < panes.length; i++) {
197
+ const pane = panes[i];
198
+ if (pane) {
199
+ const row = Math.floor(i / gridCols);
200
+ const col = i % gridCols;
201
+ geometries.push({
202
+ paneId: pane.paneId,
203
+ row,
204
+ col,
205
+ widthPercent: 50,
206
+ heightPercent: 50,
207
+ splitDirection: this.determineSplitDirection(i, row, col)
208
+ });
209
+ }
210
+ }
211
+ return geometries;
212
+ }
213
+ const firstPane = panes[0];
214
+ if (firstPane) {
215
+ geometries.push({
216
+ paneId: firstPane.paneId,
217
+ row: 0,
218
+ col: 0,
219
+ widthPercent: 100,
220
+ heightPercent: 40,
221
+ splitDirection: "none"
222
+ });
223
+ }
224
+ const bottomPanes = panes.slice(1);
225
+ const bottomCols = Math.min(bottomPanes.length, MAX_GRID_COLUMNS);
226
+ const bottomRows = Math.ceil(bottomPanes.length / bottomCols);
227
+ const cellWidth = Math.floor(100 / bottomCols);
228
+ const cellHeight = Math.floor(60 / bottomRows);
229
+ for (let i = 0; i < bottomPanes.length; i++) {
230
+ const pane = bottomPanes[i];
231
+ if (pane) {
232
+ const row = Math.floor(i / bottomCols) + 1;
233
+ const col = i % bottomCols;
234
+ const isLastInRow = col === bottomCols - 1 || i === bottomPanes.length - 1;
235
+ geometries.push({
236
+ paneId: pane.paneId,
237
+ row,
238
+ col,
239
+ widthPercent: isLastInRow ? 100 - cellWidth * col : cellWidth,
240
+ heightPercent: cellHeight,
241
+ splitDirection: this.determineSplitDirection(i + 1, row, col)
242
+ });
243
+ }
244
+ }
245
+ return geometries;
246
+ }
247
+ /**
248
+ * Determine split direction based on position in grid.
249
+ */
250
+ determineSplitDirection(index, row, col) {
251
+ if (index === 0) return "none";
252
+ if (col === 0) return "vertical";
253
+ return "horizontal";
254
+ }
255
+ /**
256
+ * Detect terminal width from environment.
257
+ */
258
+ detectTerminalWidth() {
259
+ const columns = process.stdout.columns;
260
+ return Number.isFinite(columns) && columns > 0 ? columns : DEFAULT_TERMINAL_WIDTH;
261
+ }
262
+ /**
263
+ * Detect terminal height from environment.
264
+ */
265
+ detectTerminalHeight() {
266
+ const rows = process.stdout.rows;
267
+ return Number.isFinite(rows) && rows > 0 ? rows : DEFAULT_TERMINAL_HEIGHT;
268
+ }
269
+ };
270
+
271
+ // src/panes/tmux-manager.ts
272
+ var DEFAULT_SESSION_PREFIX = "aemeathcli";
273
+ var TMUX_BINARY = "tmux";
274
+ var TmuxManager = class {
275
+ sessionPrefix;
276
+ layoutEngine;
277
+ panes = /* @__PURE__ */ new Map();
278
+ sessionName;
279
+ disposed = false;
280
+ constructor(options) {
281
+ this.sessionPrefix = options?.sessionPrefix ?? DEFAULT_SESSION_PREFIX;
282
+ this.layoutEngine = new LayoutEngine();
283
+ }
284
+ /**
285
+ * Check if tmux is available on this system.
286
+ */
287
+ async isAvailable() {
288
+ try {
289
+ const result = await execa("which", [TMUX_BINARY]);
290
+ return result.exitCode === 0;
291
+ } catch {
292
+ logger.warn("tmux binary not found on PATH");
293
+ return false;
294
+ }
295
+ }
296
+ /**
297
+ * Create a new tmux session for a team.
298
+ */
299
+ async createSession(teamName) {
300
+ this.assertNotDisposed();
301
+ if (!await this.isAvailable()) {
302
+ throw new AgentSpawnError(
303
+ teamName,
304
+ "tmux is not installed. Install tmux or use single-pane mode."
305
+ );
306
+ }
307
+ this.sessionName = `${this.sessionPrefix}-${teamName}`;
308
+ await this.killSessionSilent(this.sessionName);
309
+ try {
310
+ await execa(TMUX_BINARY, [
311
+ "new-session",
312
+ "-d",
313
+ "-s",
314
+ this.sessionName,
315
+ "-x",
316
+ String(process.stdout.columns ?? 120),
317
+ "-y",
318
+ String(process.stdout.rows ?? 40)
319
+ ]);
320
+ logger.info({ sessionName: this.sessionName }, "tmux session created");
321
+ } catch (error) {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ throw new AgentSpawnError(teamName, `Failed to create tmux session: ${message}`);
324
+ }
325
+ return this.sessionName;
326
+ }
327
+ /**
328
+ * Create split panes based on a layout configuration.
329
+ */
330
+ async createPanes(layoutConfig) {
331
+ this.assertNotDisposed();
332
+ this.assertSession();
333
+ const computed = this.layoutEngine.computeLayout(layoutConfig);
334
+ const firstGeometry = computed.panes[0];
335
+ const firstConfig = layoutConfig.panes[0];
336
+ if (firstGeometry && firstConfig) {
337
+ const tmuxPaneId = await this.getFirstPaneId();
338
+ const info = {
339
+ paneId: firstConfig.paneId,
340
+ tmuxPaneId,
341
+ agentName: firstConfig.agentName,
342
+ title: firstConfig.title
343
+ };
344
+ this.panes.set(firstConfig.paneId, info);
345
+ await this.setPaneTitle(tmuxPaneId, firstConfig.title);
346
+ getEventBus().emit("pane:created", {
347
+ paneId: firstConfig.paneId,
348
+ agentName: firstConfig.agentName
349
+ });
350
+ }
351
+ for (let i = 1; i < computed.panes.length; i++) {
352
+ const geometry = computed.panes[i];
353
+ const config = layoutConfig.panes[i];
354
+ if (!geometry || !config) continue;
355
+ const tmuxPaneId = await this.splitPane(geometry);
356
+ const info = {
357
+ paneId: config.paneId,
358
+ tmuxPaneId,
359
+ agentName: config.agentName,
360
+ title: config.title
361
+ };
362
+ this.panes.set(config.paneId, info);
363
+ await this.setPaneTitle(tmuxPaneId, config.title);
364
+ getEventBus().emit("pane:created", {
365
+ paneId: config.paneId,
366
+ agentName: config.agentName
367
+ });
368
+ }
369
+ await this.equalizeLayout();
370
+ logger.info(
371
+ { paneCount: this.panes.size, session: this.sessionName },
372
+ "All panes created"
373
+ );
374
+ return computed;
375
+ }
376
+ /**
377
+ * Send a command string to a specific pane.
378
+ */
379
+ async sendCommand(paneId, command) {
380
+ this.assertNotDisposed();
381
+ this.assertSession();
382
+ const info = this.panes.get(paneId);
383
+ if (!info) {
384
+ logger.warn({ paneId }, "Pane not found, cannot send command");
385
+ return;
386
+ }
387
+ await execa(TMUX_BINARY, [
388
+ "send-keys",
389
+ "-t",
390
+ info.tmuxPaneId,
391
+ command,
392
+ "Enter"
393
+ ]);
394
+ logger.debug({ paneId, command: command.slice(0, 80) }, "Command sent to pane");
395
+ }
396
+ /**
397
+ * Resize a pane by tmux pane target.
398
+ */
399
+ async resizePane(paneId, dimensions) {
400
+ this.assertNotDisposed();
401
+ this.assertSession();
402
+ const info = this.panes.get(paneId);
403
+ if (!info) return;
404
+ const target = info.tmuxPaneId;
405
+ if (dimensions.width !== void 0) {
406
+ await execa(TMUX_BINARY, ["resize-pane", "-t", target, "-x", String(dimensions.width)]);
407
+ }
408
+ if (dimensions.height !== void 0) {
409
+ await execa(TMUX_BINARY, ["resize-pane", "-t", target, "-y", String(dimensions.height)]);
410
+ }
411
+ }
412
+ /**
413
+ * Set the title for a tmux pane.
414
+ */
415
+ async setPaneTitle(tmuxPaneId, title) {
416
+ if (!this.sessionName) return;
417
+ try {
418
+ await execa(TMUX_BINARY, [
419
+ "select-pane",
420
+ "-t",
421
+ tmuxPaneId,
422
+ "-T",
423
+ title
424
+ ]);
425
+ } catch {
426
+ logger.debug({ tmuxPaneId, title }, "Failed to set pane title (non-fatal)");
427
+ }
428
+ }
429
+ /**
430
+ * Kill a specific pane.
431
+ */
432
+ async killPane(paneId) {
433
+ this.assertNotDisposed();
434
+ const info = this.panes.get(paneId);
435
+ if (!info || !this.sessionName) return;
436
+ try {
437
+ await execa(TMUX_BINARY, [
438
+ "kill-pane",
439
+ "-t",
440
+ info.tmuxPaneId
441
+ ]);
442
+ this.panes.delete(paneId);
443
+ getEventBus().emit("pane:closed", { paneId });
444
+ logger.debug({ paneId }, "Pane killed");
445
+ } catch {
446
+ logger.debug({ paneId }, "Failed to kill pane (may already be closed)");
447
+ }
448
+ }
449
+ /**
450
+ * Attach to the tmux session (gives control to the user).
451
+ */
452
+ async attachSession() {
453
+ this.assertNotDisposed();
454
+ this.assertSession();
455
+ await execa(TMUX_BINARY, ["attach-session", "-t", this.sessionName], {
456
+ stdio: "inherit"
457
+ });
458
+ }
459
+ /**
460
+ * Check if the session still exists.
461
+ */
462
+ async isSessionAlive() {
463
+ if (!this.sessionName) return false;
464
+ try {
465
+ await execa(TMUX_BINARY, ["has-session", "-t", this.sessionName]);
466
+ return true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ /**
472
+ * Destroy the entire tmux session and all panes.
473
+ */
474
+ async destroy() {
475
+ if (this.disposed) return;
476
+ this.disposed = true;
477
+ if (this.sessionName) {
478
+ await this.killSessionSilent(this.sessionName);
479
+ for (const paneId of this.panes.keys()) {
480
+ getEventBus().emit("pane:closed", { paneId });
481
+ }
482
+ this.panes.clear();
483
+ logger.info({ sessionName: this.sessionName }, "tmux session destroyed");
484
+ this.sessionName = void 0;
485
+ }
486
+ }
487
+ /**
488
+ * Get the current session name.
489
+ */
490
+ getSessionName() {
491
+ return this.sessionName;
492
+ }
493
+ /**
494
+ * Get all tracked pane info.
495
+ */
496
+ getPanes() {
497
+ return this.panes;
498
+ }
499
+ // ── Private Helpers ─────────────────────────────────────────────────
500
+ async splitPane(geometry) {
501
+ const splitFlag = geometry.splitDirection === "horizontal" ? "-h" : "-v";
502
+ const result = await execa(TMUX_BINARY, [
503
+ "split-window",
504
+ splitFlag,
505
+ "-t",
506
+ this.sessionName,
507
+ "-P",
508
+ "-F",
509
+ "#{pane_id}"
510
+ ]);
511
+ const tmuxPaneId = result.stdout.trim();
512
+ logger.debug(
513
+ { tmuxPaneId, direction: geometry.splitDirection },
514
+ "Pane split created"
515
+ );
516
+ return tmuxPaneId;
517
+ }
518
+ async getFirstPaneId() {
519
+ const result = await execa(TMUX_BINARY, [
520
+ "list-panes",
521
+ "-t",
522
+ this.sessionName,
523
+ "-F",
524
+ "#{pane_id}"
525
+ ]);
526
+ const firstLine = result.stdout.trim().split("\n")[0];
527
+ return firstLine ?? "%0";
528
+ }
529
+ async equalizeLayout() {
530
+ if (!this.sessionName) return;
531
+ try {
532
+ await execa(TMUX_BINARY, [
533
+ "select-layout",
534
+ "-t",
535
+ this.sessionName,
536
+ "tiled"
537
+ ]);
538
+ } catch {
539
+ logger.debug("Failed to equalize layout (non-fatal)");
540
+ }
541
+ }
542
+ async killSessionSilent(sessionName) {
543
+ try {
544
+ await execa(TMUX_BINARY, ["kill-session", "-t", sessionName]);
545
+ } catch {
546
+ }
547
+ }
548
+ assertNotDisposed() {
549
+ if (this.disposed) {
550
+ throw new AgentSpawnError("tmux", "TmuxManager has been disposed");
551
+ }
552
+ }
553
+ assertSession() {
554
+ if (!this.sessionName) {
555
+ throw new AgentSpawnError("tmux", "No active tmux session. Call createSession() first.");
556
+ }
557
+ }
558
+ };
559
+
560
+ export { LayoutEngine, TmuxManager };
561
+ //# sourceMappingURL=chunk-CARHU3DO.js.map
562
+ //# sourceMappingURL=chunk-CARHU3DO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/panes/layout-engine.ts","../src/panes/tmux-manager.ts"],"names":["firstPane","bottomPanes"],"mappings":";;;;;;AAkCA,IAAM,cAAA,GAAiB,EAAA;AACvB,IAAM,eAAA,GAAkB,EAAA;AACxB,IAAM,sBAAA,GAAyB,GAAA;AAC/B,IAAM,uBAAA,GAA0B,EAAA;AAChC,IAAM,gBAAA,GAAmB,CAAA;AAIlB,IAAM,eAAN,MAAmB;AAAA,EACP,YAAA;AAAA,EAEjB,YAAY,YAAA,EAAuC;AACjD,IAAA,IAAA,CAAK,YAAA,GAAe;AAAA,MAClB,OAAA,EAAS,YAAA,EAAc,OAAA,IAAW,IAAA,CAAK,mBAAA,EAAoB;AAAA,MAC3D,IAAA,EAAM,YAAA,EAAc,IAAA,IAAQ,IAAA,CAAK,oBAAA;AAAqB,KACxD;AACA,IAAA,MAAA,CAAO,KAAA;AAAA,MACL,EAAE,YAAA,EAAc,IAAA,CAAK,YAAA,EAAa;AAAA,MAClC;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,MAAA,EAAwC;AACpD,IAAA,MAAM,SAAA,GAAY,OAAO,KAAA,CAAM,MAAA;AAC/B,IAAA,IAAI,cAAc,CAAA,EAAG;AACnB,MAAA,OAAO;AAAA,QACL,OAAO,EAAC;AAAA,QACR,IAAA,EAAM,CAAA;AAAA,QACN,IAAA,EAAM,CAAA;AAAA,QACN,aAAA,EAAe,KAAK,YAAA,CAAa,OAAA;AAAA,QACjC,cAAA,EAAgB,KAAK,YAAA,CAAa;AAAA,OACpC;AAAA,IACF;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,GAAA;AAAA,MAC1B,CAAA;AAAA,MACA,KAAK,GAAA,CAAI,SAAA,EAAW,OAAO,QAAA,EAAU,IAAA,CAAK,aAAa;AAAA,KACzD;AACA,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,KAAW,MAAA,GAC7B,KAAK,iBAAA,CAAkB,cAAc,IACrC,MAAA,CAAO,MAAA;AAEX,IAAA,MAAM,aAAa,IAAA,CAAK,iBAAA;AAAA,MACtB,MAAA,CAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAAA,MACpC,MAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,MAAM,EAAE,QAAA,EAAU,QAAA,KAAa,IAAA,CAAK,iBAAA,CAAkB,gBAAgB,MAAM,CAAA;AAE5E,IAAA,MAAA,CAAO,IAAA;AAAA,MACL,EAAE,MAAA,EAAQ,SAAA,EAAW,cAAA,EAAgB,UAAU,QAAA,EAAS;AAAA,MACxD;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,UAAA;AAAA,MACP,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,QAAA;AAAA,MACN,aAAA,EAAe,KAAK,YAAA,CAAa,OAAA;AAAA,MACjC,cAAA,EAAgB,KAAK,YAAA,CAAa;AAAA,KACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,SAAA,EAAgD;AAChE,IAAA,IAAI,SAAA,IAAa,GAAG,OAAO,YAAA;AAC3B,IAAA,IAAI,SAAA,KAAc,GAAG,OAAO,YAAA;AAC5B,IAAA,IAAI,SAAA,IAAa,GAAG,OAAO,MAAA;AAC3B,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,GAAsB;AACpB,IAAA,MAAM,aAAa,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,UAAU,cAAc,CAAA;AACxE,IAAA,MAAM,cAAc,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,OAAO,eAAe,CAAA;AACvE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,UAAA,GAAa,WAAW,CAAA;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAA,EAA4B;AACtC,IAAA,OAAO,SAAA,IAAa,KAAK,WAAA,EAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAA,CACN,WACA,MAAA,EACwC;AACxC,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,YAAA;AACH,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,QAAA,EAAU,SAAA,EAAU;AAAA,MAC5C,KAAK,UAAA;AACH,QAAA,OAAO,EAAE,QAAA,EAAU,SAAA,EAAW,QAAA,EAAU,CAAA,EAAE;AAAA,MAC5C,KAAK,MAAA;AACH,QAAA,OAAO,IAAA,CAAK,gBAAgB,SAAS,CAAA;AAAA;AACzC,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,gBAAgB,SAAA,EAA2D;AACjF,IAAA,IAAI,aAAa,CAAA,EAAG,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,UAAU,CAAA,EAAE;AACtD,IAAA,IAAI,cAAc,CAAA,EAAG,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,UAAU,CAAA,EAAE;AACvD,IAAA,IAAI,aAAa,CAAA,EAAG,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,UAAU,CAAA,EAAE;AAEtD,IAAA,MAAM,kBAAkB,SAAA,GAAY,CAAA;AACpC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,eAAA,EAAiB,gBAAgB,CAAA;AAC3D,IAAA,MAAM,QAAA,GAAW,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,kBAAkB,QAAQ,CAAA;AACzD,IAAA,OAAO,EAAE,UAAU,QAAA,EAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAA,CACN,KAAA,EACA,MAAA,EACA,SAAA,EACiB;AACjB,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,YAAA;AACH,QAAA,OAAO,IAAA,CAAK,wBAAwB,KAAK,CAAA;AAAA,MAC3C,KAAK,UAAA;AACH,QAAA,OAAO,IAAA,CAAK,sBAAsB,KAAK,CAAA;AAAA,MACzC,KAAK,MAAA;AACH,QAAA,OAAO,IAAA,CAAK,iBAAA,CAAkB,KAAA,EAAO,SAAS,CAAA;AAAA;AAClD,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAAwB,KAAA,EAAgD;AAC9E,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,MAAM,MAAM,CAAA;AAClD,IAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,MAAW;AAAA,MACjC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAA,EAAK,CAAA;AAAA,MACL,GAAA,EAAK,KAAA;AAAA,MACL,YAAA,EAAc,UAAU,KAAA,CAAM,MAAA,GAAS,IACnC,GAAA,GAAM,YAAA,IAAgB,KAAA,CAAM,MAAA,GAAS,CAAA,CAAA,GACrC,YAAA;AAAA,MACJ,aAAA,EAAe,GAAA;AAAA,MACf,cAAA,EAAgB,KAAA,KAAU,CAAA,GAAI,MAAA,GAAkB;AAAA,KAClD,CAAE,CAAA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,KAAA,EAAgD;AAC5E,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,MAAM,MAAM,CAAA;AACnD,IAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,MAAW;AAAA,MACjC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAA,EAAK,KAAA;AAAA,MACL,GAAA,EAAK,CAAA;AAAA,MACL,YAAA,EAAc,GAAA;AAAA,MACd,aAAA,EAAe,UAAU,KAAA,CAAM,MAAA,GAAS,IACpC,GAAA,GAAM,aAAA,IAAiB,KAAA,CAAM,MAAA,GAAS,CAAA,CAAA,GACtC,aAAA;AAAA,MACJ,cAAA,EAAgB,KAAA,KAAU,CAAA,GAAI,MAAA,GAAkB;AAAA,KAClD,CAAE,CAAA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,iBAAA,CACN,OACA,SAAA,EACiB;AACjB,IAAA,MAAM,aAA8B,EAAC;AAErC,IAAA,IAAI,aAAa,CAAA,EAAG;AAClB,MAAA,OAAO,IAAA,CAAK,wBAAwB,KAAK,CAAA;AAAA,IAC3C;AAEA,IAAA,IAAI,cAAc,CAAA,EAAG;AAEnB,MAAA,MAAMA,UAAAA,GAAY,MAAM,CAAC,CAAA;AACzB,MAAA,IAAIA,UAAAA,EAAW;AACb,QAAA,UAAA,CAAW,IAAA,CAAK;AAAA,UACd,QAAQA,UAAAA,CAAU,MAAA;AAAA,UAClB,GAAA,EAAK,CAAA;AAAA,UACL,GAAA,EAAK,CAAA;AAAA,UACL,YAAA,EAAc,GAAA;AAAA,UACd,aAAA,EAAe,EAAA;AAAA,UACf,cAAA,EAAgB;AAAA,SACjB,CAAA;AAAA,MACH;AACA,MAAA,MAAMC,YAAAA,GAAc,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AACjC,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAIA,YAAAA,CAAY,QAAQ,CAAA,EAAA,EAAK;AAC3C,QAAA,MAAM,IAAA,GAAOA,aAAY,CAAC,CAAA;AAC1B,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,UAAA,CAAW,IAAA,CAAK;AAAA,YACd,QAAQ,IAAA,CAAK,MAAA;AAAA,YACb,GAAA,EAAK,CAAA;AAAA,YACL,GAAA,EAAK,CAAA;AAAA,YACL,YAAA,EAAc,EAAA;AAAA,YACd,aAAA,EAAe,EAAA;AAAA,YACf,cAAA,EAAgB,CAAA,KAAM,CAAA,GAAI,UAAA,GAAa;AAAA,WACxC,CAAA;AAAA,QACH;AAAA,MACF;AACA,MAAA,OAAO,UAAA;AAAA,IACT;AAEA,IAAA,IAAI,cAAc,CAAA,EAAG;AAEnB,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,IAAA,CAAK,gBAAgB,CAAC,CAAA;AAC3C,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,QAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,CAAA,GAAI,QAAQ,CAAA;AACnC,UAAA,MAAM,MAAM,CAAA,GAAI,QAAA;AAChB,UAAA,UAAA,CAAW,IAAA,CAAK;AAAA,YACd,QAAQ,IAAA,CAAK,MAAA;AAAA,YACb,GAAA;AAAA,YACA,GAAA;AAAA,YACA,YAAA,EAAc,EAAA;AAAA,YACd,aAAA,EAAe,EAAA;AAAA,YACf,cAAA,EAAgB,IAAA,CAAK,uBAAA,CAAwB,CAAA,EAAG,KAAK,GAAG;AAAA,WACzD,CAAA;AAAA,QACH;AAAA,MACF;AACA,MAAA,OAAO,UAAA;AAAA,IACT;AAGA,IAAA,MAAM,SAAA,GAAY,MAAM,CAAC,CAAA;AACzB,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,UAAA,CAAW,IAAA,CAAK;AAAA,QACd,QAAQ,SAAA,CAAU,MAAA;AAAA,QAClB,GAAA,EAAK,CAAA;AAAA,QACL,GAAA,EAAK,CAAA;AAAA,QACL,YAAA,EAAc,GAAA;AAAA,QACd,aAAA,EAAe,EAAA;AAAA,QACf,cAAA,EAAgB;AAAA,OACjB,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,WAAA,GAAc,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AACjC,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,WAAA,CAAY,QAAQ,gBAAgB,CAAA;AAChE,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,WAAA,CAAY,SAAS,UAAU,CAAA;AAC5D,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,UAAU,CAAA;AAC7C,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,UAAU,CAAA;AAE7C,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,WAAA,CAAY,QAAQ,CAAA,EAAA,EAAK;AAC3C,MAAA,MAAM,IAAA,GAAO,YAAY,CAAC,CAAA;AAC1B,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,CAAA,GAAI,UAAU,CAAA,GAAI,CAAA;AACzC,QAAA,MAAM,MAAM,CAAA,GAAI,UAAA;AAChB,QAAA,MAAM,cAAc,GAAA,KAAQ,UAAA,GAAa,CAAA,IAAK,CAAA,KAAM,YAAY,MAAA,GAAS,CAAA;AACzE,QAAA,UAAA,CAAW,IAAA,CAAK;AAAA,UACd,QAAQ,IAAA,CAAK,MAAA;AAAA,UACb,GAAA;AAAA,UACA,GAAA;AAAA,UACA,YAAA,EAAc,WAAA,GAAc,GAAA,GAAM,SAAA,GAAY,GAAA,GAAM,SAAA;AAAA,UACpD,aAAA,EAAe,UAAA;AAAA,UACf,gBAAgB,IAAA,CAAK,uBAAA,CAAwB,CAAA,GAAI,CAAA,EAAG,KAAK,GAAG;AAAA,SAC7D,CAAA;AAAA,MACH;AAAA,IACF;AAEA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAA,CACN,KAAA,EACA,GAAA,EACA,GAAA,EACoC;AACpC,IAAA,IAAI,KAAA,KAAU,GAAG,OAAO,MAAA;AACxB,IAAA,IAAI,GAAA,KAAQ,GAAG,OAAO,UAAA;AACtB,IAAA,OAAO,YAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAA,GAA8B;AACpC,IAAA,MAAM,OAAA,GAAU,QAAQ,MAAA,CAAO,OAAA;AAC/B,IAAA,OAAO,OAAO,QAAA,CAAS,OAAO,CAAA,IAAK,OAAA,GAAU,IAAI,OAAA,GAAU,sBAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAA,GAA+B;AACrC,IAAA,MAAM,IAAA,GAAO,QAAQ,MAAA,CAAO,IAAA;AAC5B,IAAA,OAAO,OAAO,QAAA,CAAS,IAAI,CAAA,IAAK,IAAA,GAAO,IAAI,IAAA,GAAO,uBAAA;AAAA,EACpD;AACF;;;ACjUA,IAAM,sBAAA,GAAyB,YAAA;AAC/B,IAAM,WAAA,GAAc,MAAA;AAIb,IAAM,cAAN,MAAkB;AAAA,EACN,aAAA;AAAA,EACA,YAAA;AAAA,EACA,KAAA,uBAAY,GAAA,EAA2B;AAAA,EAChD,WAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EAEnB,YAAY,OAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,aAAA,GAAgB,SAAS,aAAA,IAAiB,sBAAA;AAC/C,IAAA,IAAA,CAAK,YAAA,GAAe,IAAI,YAAA,EAAa;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAA,GAAgC;AACpC,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,KAAA,CAAM,OAAA,EAAS,CAAC,WAAW,CAAC,CAAA;AACjD,MAAA,OAAO,OAAO,QAAA,KAAa,CAAA;AAAA,IAC7B,CAAA,CAAA,MAAQ;AACN,MAAA,MAAA,CAAO,KAAK,+BAA+B,CAAA;AAC3C,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAAA,EAAmC;AACrD,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,IAAI,CAAE,MAAM,IAAA,CAAK,WAAA,EAAY,EAAI;AAC/B,MAAA,MAAM,IAAI,eAAA;AAAA,QACR,QAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,WAAA,GAAc,CAAA,EAAG,IAAA,CAAK,aAAa,IAAI,QAAQ,CAAA,CAAA;AAGpD,IAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,IAAA,CAAK,WAAW,CAAA;AAE7C,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa;AAAA,QACvB,aAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QAAM,IAAA,CAAK,WAAA;AAAA,QACX,IAAA;AAAA,QAAM,MAAA,CAAO,OAAA,CAAQ,MAAA,CAAO,OAAA,IAAW,GAAG,CAAA;AAAA,QAC1C,IAAA;AAAA,QAAM,MAAA,CAAO,OAAA,CAAQ,MAAA,CAAO,IAAA,IAAQ,EAAE;AAAA,OACvC,CAAA;AACD,MAAA,MAAA,CAAO,KAAK,EAAE,WAAA,EAAa,IAAA,CAAK,WAAA,IAAe,sBAAsB,CAAA;AAAA,IACvE,SAAS,KAAA,EAAgB;AACvB,MAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,MAAA,MAAM,IAAI,eAAA,CAAgB,QAAA,EAAU,CAAA,+BAAA,EAAkC,OAAO,CAAA,CAAE,CAAA;AAAA,IACjF;AAEA,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,YAAA,EAAuD;AACvE,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,YAAA,CAAa,aAAA,CAAc,YAAY,CAAA;AAG7D,IAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AACtC,IAAA,MAAM,WAAA,GAAc,YAAA,CAAa,KAAA,CAAM,CAAC,CAAA;AACxC,IAAA,IAAI,iBAAiB,WAAA,EAAa;AAChC,MAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,cAAA,EAAe;AAC7C,MAAA,MAAM,IAAA,GAAsB;AAAA,QAC1B,QAAQ,WAAA,CAAY,MAAA;AAAA,QACpB,UAAA;AAAA,QACA,WAAW,WAAA,CAAY,SAAA;AAAA,QACvB,OAAO,WAAA,CAAY;AAAA,OACrB;AACA,MAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,WAAA,CAAY,MAAA,EAAQ,IAAI,CAAA;AACvC,MAAA,MAAM,IAAA,CAAK,YAAA,CAAa,UAAA,EAAY,WAAA,CAAY,KAAK,CAAA;AACrD,MAAA,WAAA,EAAY,CAAE,KAAK,cAAA,EAAgB;AAAA,QACjC,QAAQ,WAAA,CAAY,MAAA;AAAA,QACpB,WAAW,WAAA,CAAY;AAAA,OACxB,CAAA;AAAA,IACH;AAGA,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AAC9C,MAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AACjC,MAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,CAAC,CAAA;AACnC,MAAA,IAAI,CAAC,QAAA,IAAY,CAAC,MAAA,EAAQ;AAE1B,MAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA;AAChD,MAAA,MAAM,IAAA,GAAsB;AAAA,QAC1B,QAAQ,MAAA,CAAO,MAAA;AAAA,QACf,UAAA;AAAA,QACA,WAAW,MAAA,CAAO,SAAA;AAAA,QAClB,OAAO,MAAA,CAAO;AAAA,OAChB;AACA,MAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,IAAI,CAAA;AAClC,MAAA,MAAM,IAAA,CAAK,YAAA,CAAa,UAAA,EAAY,MAAA,CAAO,KAAK,CAAA;AAChD,MAAA,WAAA,EAAY,CAAE,KAAK,cAAA,EAAgB;AAAA,QACjC,QAAQ,MAAA,CAAO,MAAA;AAAA,QACf,WAAW,MAAA,CAAO;AAAA,OACnB,CAAA;AAAA,IACH;AAGA,IAAA,MAAM,KAAK,cAAA,EAAe;AAE1B,IAAA,MAAA,CAAO,IAAA;AAAA,MACL,EAAE,SAAA,EAAW,IAAA,CAAK,MAAM,IAAA,EAAM,OAAA,EAAS,KAAK,WAAA,EAAY;AAAA,MACxD;AAAA,KACF;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAA,CAAY,MAAA,EAAgB,OAAA,EAAgC;AAChE,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAClC,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,MAAA,EAAO,EAAG,qCAAqC,CAAA;AAC7D,MAAA;AAAA,IACF;AAKA,IAAA,MAAM,MAAM,WAAA,EAAa;AAAA,MACvB,WAAA;AAAA,MACA,IAAA;AAAA,MAAM,IAAA,CAAK,UAAA;AAAA,MACX,OAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAA,CAAO,KAAA,CAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,OAAA,CAAQ,MAAM,CAAA,EAAG,EAAE,CAAA,EAAE,EAAG,sBAAsB,CAAA;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAA,CACJ,MAAA,EACA,UAAA,EACe;AACf,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAClC,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AAEpB,IAAA,IAAI,UAAA,CAAW,UAAU,MAAA,EAAW;AAClC,MAAA,MAAM,KAAA,CAAM,WAAA,EAAa,CAAC,aAAA,EAAe,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAC,CAAC,CAAA;AAAA,IACxF;AACA,IAAA,IAAI,UAAA,CAAW,WAAW,MAAA,EAAW;AACnC,MAAA,MAAM,KAAA,CAAM,WAAA,EAAa,CAAC,aAAA,EAAe,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,MAAA,CAAO,UAAA,CAAW,MAAM,CAAC,CAAC,CAAA;AAAA,IACzF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAAA,CAAa,UAAA,EAAoB,KAAA,EAA8B;AAC3E,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa;AAAA,QACvB,aAAA;AAAA,QACA,IAAA;AAAA,QAAM,UAAA;AAAA,QACN,IAAA;AAAA,QAAM;AAAA,OACP,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AACN,MAAA,MAAA,CAAO,KAAA,CAAM,EAAE,UAAA,EAAY,KAAA,IAAS,sCAAsC,CAAA;AAAA,IAC5E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAAA,EAA+B;AAC5C,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAEvB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAClC,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,IAAA,CAAK,WAAA,EAAa;AAEhC,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa;AAAA,QACvB,WAAA;AAAA,QACA,IAAA;AAAA,QAAM,IAAA,CAAK;AAAA,OACZ,CAAA;AACD,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,MAAM,CAAA;AACxB,MAAA,WAAA,EAAY,CAAE,IAAA,CAAK,aAAA,EAAe,EAAE,QAAQ,CAAA;AAC5C,MAAA,MAAA,CAAO,KAAA,CAAM,EAAE,MAAA,EAAO,EAAG,aAAa,CAAA;AAAA,IACxC,CAAA,CAAA,MAAQ;AACN,MAAA,MAAA,CAAO,KAAA,CAAM,EAAE,MAAA,EAAO,EAAG,6CAA6C,CAAA;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAA,GAA+B;AACnC,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,MAAM,MAAM,WAAA,EAAa,CAAC,kBAAkB,IAAA,EAAM,IAAA,CAAK,WAAY,CAAA,EAAG;AAAA,MACpE,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAA,GAAmC;AACvC,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAa,OAAO,KAAA;AAC9B,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa,CAAC,eAAe,IAAA,EAAM,IAAA,CAAK,WAAW,CAAC,CAAA;AAChE,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,KAAK,QAAA,EAAU;AACnB,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAEhB,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,IAAA,CAAK,WAAW,CAAA;AAE7C,MAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK,EAAG;AACtC,QAAA,WAAA,EAAY,CAAE,IAAA,CAAK,aAAA,EAAe,EAAE,QAAQ,CAAA;AAAA,MAC9C;AACA,MAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAEjB,MAAA,MAAA,CAAO,KAAK,EAAE,WAAA,EAAa,IAAA,CAAK,WAAA,IAAe,wBAAwB,CAAA;AACvE,MAAA,IAAA,CAAK,WAAA,GAAc,MAAA;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAA,GAAqC;AACnC,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAA+C;AAC7C,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA,EAIA,MAAc,UAAU,QAAA,EAA0C;AAChE,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,cAAA,KAAmB,YAAA,GAAe,IAAA,GAAO,IAAA;AAEpE,IAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,WAAA,EAAa;AAAA,MACtC,cAAA;AAAA,MACA,SAAA;AAAA,MACA,IAAA;AAAA,MAAM,IAAA,CAAK,WAAA;AAAA,MACX,IAAA;AAAA,MACA,IAAA;AAAA,MAAM;AAAA,KACP,CAAA;AAED,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,MAAA,CAAO,IAAA,EAAK;AACtC,IAAA,MAAA,CAAO,KAAA;AAAA,MACL,EAAE,UAAA,EAAY,SAAA,EAAW,QAAA,CAAS,cAAA,EAAe;AAAA,MACjD;AAAA,KACF;AACA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA,EAEA,MAAc,cAAA,GAAkC;AAC9C,IAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,WAAA,EAAa;AAAA,MACtC,YAAA;AAAA,MACA,IAAA;AAAA,MAAM,IAAA,CAAK,WAAA;AAAA,MACX,IAAA;AAAA,MAAM;AAAA,KACP,CAAA;AACD,IAAA,MAAM,SAAA,GAAY,OAAO,MAAA,CAAO,IAAA,GAAO,KAAA,CAAM,IAAI,EAAE,CAAC,CAAA;AACpD,IAAA,OAAO,SAAA,IAAa,IAAA;AAAA,EACtB;AAAA,EAEA,MAAc,cAAA,GAAgC;AAC5C,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa;AAAA,QACvB,eAAA;AAAA,QACA,IAAA;AAAA,QAAM,IAAA,CAAK,WAAA;AAAA,QACX;AAAA,OACD,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AACN,MAAA,MAAA,CAAO,MAAM,uCAAuC,CAAA;AAAA,IACtD;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,WAAA,EAAoC;AAClE,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,WAAA,EAAa,CAAC,cAAA,EAAgB,IAAA,EAAM,WAAW,CAAC,CAAA;AAAA,IAC9D,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,iBAAA,GAA0B;AAChC,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,MAAM,IAAI,eAAA,CAAgB,MAAA,EAAQ,+BAA+B,CAAA;AAAA,IACnE;AAAA,EACF;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,MAAA,MAAM,IAAI,eAAA,CAAgB,MAAA,EAAQ,qDAAqD,CAAA;AAAA,IACzF;AAAA,EACF;AACF","file":"chunk-CARHU3DO.js","sourcesContent":["/**\n * Layout engine for split-panel auto-layout computation per PRD section 9.2.\n * Computes pane positions based on agent count and terminal dimensions.\n */\n\nimport type { PaneLayout, IPaneConfig, ILayoutConfig } from \"../types/team.js\";\nimport { logger } from \"../utils/logger.js\";\n\n// ── Layout Geometry ─────────────────────────────────────────────────────\n\nexport interface IPaneGeometry {\n readonly paneId: string;\n readonly row: number;\n readonly col: number;\n readonly widthPercent: number;\n readonly heightPercent: number;\n readonly splitDirection: \"horizontal\" | \"vertical\" | \"none\";\n}\n\nexport interface IComputedLayout {\n readonly panes: readonly IPaneGeometry[];\n readonly rows: number;\n readonly cols: number;\n readonly terminalWidth: number;\n readonly terminalHeight: number;\n}\n\ninterface ITerminalSize {\n readonly columns: number;\n readonly rows: number;\n}\n\n// ── Constants ───────────────────────────────────────────────────────────\n\nconst MIN_PANE_WIDTH = 40;\nconst MIN_PANE_HEIGHT = 10;\nconst DEFAULT_TERMINAL_WIDTH = 120;\nconst DEFAULT_TERMINAL_HEIGHT = 40;\nconst MAX_GRID_COLUMNS = 3;\n\n// ── Layout Engine ───────────────────────────────────────────────────────\n\nexport class LayoutEngine {\n private readonly terminalSize: ITerminalSize;\n\n constructor(terminalSize?: Partial<ITerminalSize>) {\n this.terminalSize = {\n columns: terminalSize?.columns ?? this.detectTerminalWidth(),\n rows: terminalSize?.rows ?? this.detectTerminalHeight(),\n };\n logger.debug(\n { terminalSize: this.terminalSize },\n \"LayoutEngine initialized\",\n );\n }\n\n /**\n * Compute the full layout for a set of pane configs.\n */\n computeLayout(config: ILayoutConfig): IComputedLayout {\n const paneCount = config.panes.length;\n if (paneCount === 0) {\n return {\n panes: [],\n rows: 0,\n cols: 0,\n terminalWidth: this.terminalSize.columns,\n terminalHeight: this.terminalSize.rows,\n };\n }\n\n const effectiveCount = Math.max(\n 1,\n Math.min(paneCount, config.maxPanes, this.getMaxPanes()),\n );\n const layout = config.layout === \"auto\"\n ? this.resolveAutoLayout(effectiveCount)\n : config.layout;\n\n const geometries = this.computeGeometries(\n config.panes.slice(0, effectiveCount),\n layout,\n effectiveCount,\n );\n\n const { gridRows, gridCols } = this.getGridDimensions(effectiveCount, layout);\n\n logger.info(\n { layout, paneCount: effectiveCount, gridRows, gridCols },\n \"Layout computed\",\n );\n\n return {\n panes: geometries,\n rows: gridRows,\n cols: gridCols,\n terminalWidth: this.terminalSize.columns,\n terminalHeight: this.terminalSize.rows,\n };\n }\n\n /**\n * Determine the best auto-layout for a given pane count (PRD section 9.2).\n */\n resolveAutoLayout(paneCount: number): Exclude<PaneLayout, \"auto\"> {\n if (paneCount <= 1) return \"horizontal\";\n if (paneCount === 2) return \"horizontal\";\n if (paneCount <= 4) return \"grid\";\n return \"grid\";\n }\n\n /**\n * Get the maximum number of panes the terminal can support.\n */\n getMaxPanes(): number {\n const maxByWidth = Math.floor(this.terminalSize.columns / MIN_PANE_WIDTH);\n const maxByHeight = Math.floor(this.terminalSize.rows / MIN_PANE_HEIGHT);\n return Math.max(1, maxByWidth * maxByHeight);\n }\n\n /**\n * Check if the terminal is large enough for the requested pane count.\n */\n canFitPanes(paneCount: number): boolean {\n return paneCount <= this.getMaxPanes();\n }\n\n /**\n * Get the grid dimensions for a given pane count and layout type.\n */\n private getGridDimensions(\n paneCount: number,\n layout: Exclude<PaneLayout, \"auto\">,\n ): { gridRows: number; gridCols: number } {\n switch (layout) {\n case \"horizontal\":\n return { gridRows: 1, gridCols: paneCount };\n case \"vertical\":\n return { gridRows: paneCount, gridCols: 1 };\n case \"grid\":\n return this.computeGridSize(paneCount);\n }\n }\n\n /**\n * Compute grid rows and columns for a given pane count.\n * PRD section 9.2:\n * 2 agents → horizontal split (50/50) → 1x2\n * 3 agents → 1 top + 2 bottom → 2 rows\n * 4 agents → 2x2 grid\n * 5+ agents → leader top + grid bottom\n */\n private computeGridSize(paneCount: number): { gridRows: number; gridCols: number } {\n if (paneCount <= 1) return { gridRows: 1, gridCols: 1 };\n if (paneCount === 2) return { gridRows: 1, gridCols: 2 };\n if (paneCount <= 4) return { gridRows: 2, gridCols: 2 };\n\n const bottomPaneCount = paneCount - 1;\n const gridCols = Math.min(bottomPaneCount, MAX_GRID_COLUMNS);\n const gridRows = 1 + Math.ceil(bottomPaneCount / gridCols);\n return { gridRows, gridCols };\n }\n\n /**\n * Compute per-pane geometries based on layout type.\n */\n private computeGeometries(\n panes: readonly IPaneConfig[],\n layout: Exclude<PaneLayout, \"auto\">,\n paneCount: number,\n ): IPaneGeometry[] {\n switch (layout) {\n case \"horizontal\":\n return this.computeHorizontalLayout(panes);\n case \"vertical\":\n return this.computeVerticalLayout(panes);\n case \"grid\":\n return this.computeGridLayout(panes, paneCount);\n }\n }\n\n /**\n * Horizontal split: all panes side by side.\n */\n private computeHorizontalLayout(panes: readonly IPaneConfig[]): IPaneGeometry[] {\n const widthPercent = Math.floor(100 / panes.length);\n return panes.map((pane, index) => ({\n paneId: pane.paneId,\n row: 0,\n col: index,\n widthPercent: index === panes.length - 1\n ? 100 - widthPercent * (panes.length - 1)\n : widthPercent,\n heightPercent: 100,\n splitDirection: index === 0 ? \"none\" as const : \"horizontal\" as const,\n }));\n }\n\n /**\n * Vertical split: all panes stacked.\n */\n private computeVerticalLayout(panes: readonly IPaneConfig[]): IPaneGeometry[] {\n const heightPercent = Math.floor(100 / panes.length);\n return panes.map((pane, index) => ({\n paneId: pane.paneId,\n row: index,\n col: 0,\n widthPercent: 100,\n heightPercent: index === panes.length - 1\n ? 100 - heightPercent * (panes.length - 1)\n : heightPercent,\n splitDirection: index === 0 ? \"none\" as const : \"vertical\" as const,\n }));\n }\n\n /**\n * Grid layout per PRD section 9.2 rules:\n * 3 agents → leader spans top, 2 on bottom\n * 4 agents → 2x2 even grid\n * 5+ agents → leader spans top, rest in grid below\n */\n private computeGridLayout(\n panes: readonly IPaneConfig[],\n paneCount: number,\n ): IPaneGeometry[] {\n const geometries: IPaneGeometry[] = [];\n\n if (paneCount <= 2) {\n return this.computeHorizontalLayout(panes);\n }\n\n if (paneCount === 3) {\n // 1 top (leader, full width) + 2 bottom\n const firstPane = panes[0];\n if (firstPane) {\n geometries.push({\n paneId: firstPane.paneId,\n row: 0,\n col: 0,\n widthPercent: 100,\n heightPercent: 50,\n splitDirection: \"none\",\n });\n }\n const bottomPanes = panes.slice(1);\n for (let i = 0; i < bottomPanes.length; i++) {\n const pane = bottomPanes[i];\n if (pane) {\n geometries.push({\n paneId: pane.paneId,\n row: 1,\n col: i,\n widthPercent: 50,\n heightPercent: 50,\n splitDirection: i === 0 ? \"vertical\" : \"horizontal\",\n });\n }\n }\n return geometries;\n }\n\n if (paneCount === 4) {\n // 2x2 even grid\n const { gridCols } = this.computeGridSize(4);\n for (let i = 0; i < panes.length; i++) {\n const pane = panes[i];\n if (pane) {\n const row = Math.floor(i / gridCols);\n const col = i % gridCols;\n geometries.push({\n paneId: pane.paneId,\n row,\n col,\n widthPercent: 50,\n heightPercent: 50,\n splitDirection: this.determineSplitDirection(i, row, col),\n });\n }\n }\n return geometries;\n }\n\n // 5+ agents: leader top + grid bottom\n const firstPane = panes[0];\n if (firstPane) {\n geometries.push({\n paneId: firstPane.paneId,\n row: 0,\n col: 0,\n widthPercent: 100,\n heightPercent: 40,\n splitDirection: \"none\",\n });\n }\n\n const bottomPanes = panes.slice(1);\n const bottomCols = Math.min(bottomPanes.length, MAX_GRID_COLUMNS);\n const bottomRows = Math.ceil(bottomPanes.length / bottomCols);\n const cellWidth = Math.floor(100 / bottomCols);\n const cellHeight = Math.floor(60 / bottomRows);\n\n for (let i = 0; i < bottomPanes.length; i++) {\n const pane = bottomPanes[i];\n if (pane) {\n const row = Math.floor(i / bottomCols) + 1;\n const col = i % bottomCols;\n const isLastInRow = col === bottomCols - 1 || i === bottomPanes.length - 1;\n geometries.push({\n paneId: pane.paneId,\n row,\n col,\n widthPercent: isLastInRow ? 100 - cellWidth * col : cellWidth,\n heightPercent: cellHeight,\n splitDirection: this.determineSplitDirection(i + 1, row, col),\n });\n }\n }\n\n return geometries;\n }\n\n /**\n * Determine split direction based on position in grid.\n */\n private determineSplitDirection(\n index: number,\n row: number,\n col: number,\n ): \"horizontal\" | \"vertical\" | \"none\" {\n if (index === 0) return \"none\";\n if (col === 0) return \"vertical\";\n return \"horizontal\";\n }\n\n /**\n * Detect terminal width from environment.\n */\n private detectTerminalWidth(): number {\n const columns = process.stdout.columns;\n return Number.isFinite(columns) && columns > 0 ? columns : DEFAULT_TERMINAL_WIDTH;\n }\n\n /**\n * Detect terminal height from environment.\n */\n private detectTerminalHeight(): number {\n const rows = process.stdout.rows;\n return Number.isFinite(rows) && rows > 0 ? rows : DEFAULT_TERMINAL_HEIGHT;\n }\n}\n","/**\n * TmuxManager — Programmatic tmux control per PRD section 9.2.\n * Creates sessions, splits panes, sends commands, manages lifecycle.\n */\n\nimport { execa } from \"execa\";\nimport type { ILayoutConfig, IPaneConfig } from \"../types/team.js\";\nimport type { IComputedLayout, IPaneGeometry } from \"./layout-engine.js\";\nimport { LayoutEngine } from \"./layout-engine.js\";\nimport { getEventBus } from \"../core/event-bus.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { AgentSpawnError } from \"../types/errors.js\";\n\n// ── Types ───────────────────────────────────────────────────────────────\n\nexport interface ITmuxPaneInfo {\n readonly paneId: string;\n readonly tmuxPaneId: string;\n readonly agentName: string;\n readonly title: string;\n}\n\ninterface ITmuxManagerOptions {\n readonly sessionPrefix?: string;\n}\n\n// ── Constants ───────────────────────────────────────────────────────────\n\nconst DEFAULT_SESSION_PREFIX = \"aemeathcli\";\nconst TMUX_BINARY = \"tmux\";\n\n// ── TmuxManager ─────────────────────────────────────────────────────────\n\nexport class TmuxManager {\n private readonly sessionPrefix: string;\n private readonly layoutEngine: LayoutEngine;\n private readonly panes = new Map<string, ITmuxPaneInfo>();\n private sessionName: string | undefined;\n private disposed = false;\n\n constructor(options?: ITmuxManagerOptions) {\n this.sessionPrefix = options?.sessionPrefix ?? DEFAULT_SESSION_PREFIX;\n this.layoutEngine = new LayoutEngine();\n }\n\n /**\n * Check if tmux is available on this system.\n */\n async isAvailable(): Promise<boolean> {\n try {\n const result = await execa(\"which\", [TMUX_BINARY]);\n return result.exitCode === 0;\n } catch {\n logger.warn(\"tmux binary not found on PATH\");\n return false;\n }\n }\n\n /**\n * Create a new tmux session for a team.\n */\n async createSession(teamName: string): Promise<string> {\n this.assertNotDisposed();\n\n if (!(await this.isAvailable())) {\n throw new AgentSpawnError(\n teamName,\n \"tmux is not installed. Install tmux or use single-pane mode.\",\n );\n }\n\n this.sessionName = `${this.sessionPrefix}-${teamName}`;\n\n // Kill any existing session with the same name\n await this.killSessionSilent(this.sessionName);\n\n try {\n await execa(TMUX_BINARY, [\n \"new-session\",\n \"-d\",\n \"-s\", this.sessionName,\n \"-x\", String(process.stdout.columns ?? 120),\n \"-y\", String(process.stdout.rows ?? 40),\n ]);\n logger.info({ sessionName: this.sessionName }, \"tmux session created\");\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n throw new AgentSpawnError(teamName, `Failed to create tmux session: ${message}`);\n }\n\n return this.sessionName;\n }\n\n /**\n * Create split panes based on a layout configuration.\n */\n async createPanes(layoutConfig: ILayoutConfig): Promise<IComputedLayout> {\n this.assertNotDisposed();\n this.assertSession();\n\n const computed = this.layoutEngine.computeLayout(layoutConfig);\n\n // First pane is already the initial pane in the session\n const firstGeometry = computed.panes[0];\n const firstConfig = layoutConfig.panes[0];\n if (firstGeometry && firstConfig) {\n const tmuxPaneId = await this.getFirstPaneId();\n const info: ITmuxPaneInfo = {\n paneId: firstConfig.paneId,\n tmuxPaneId,\n agentName: firstConfig.agentName,\n title: firstConfig.title,\n };\n this.panes.set(firstConfig.paneId, info);\n await this.setPaneTitle(tmuxPaneId, firstConfig.title);\n getEventBus().emit(\"pane:created\", {\n paneId: firstConfig.paneId,\n agentName: firstConfig.agentName,\n });\n }\n\n // Create remaining panes via splits\n for (let i = 1; i < computed.panes.length; i++) {\n const geometry = computed.panes[i];\n const config = layoutConfig.panes[i];\n if (!geometry || !config) continue;\n\n const tmuxPaneId = await this.splitPane(geometry);\n const info: ITmuxPaneInfo = {\n paneId: config.paneId,\n tmuxPaneId,\n agentName: config.agentName,\n title: config.title,\n };\n this.panes.set(config.paneId, info);\n await this.setPaneTitle(tmuxPaneId, config.title);\n getEventBus().emit(\"pane:created\", {\n paneId: config.paneId,\n agentName: config.agentName,\n });\n }\n\n // Equalize layout after all splits\n await this.equalizeLayout();\n\n logger.info(\n { paneCount: this.panes.size, session: this.sessionName },\n \"All panes created\",\n );\n\n return computed;\n }\n\n /**\n * Send a command string to a specific pane.\n */\n async sendCommand(paneId: string, command: string): Promise<void> {\n this.assertNotDisposed();\n this.assertSession();\n\n const info = this.panes.get(paneId);\n if (!info) {\n logger.warn({ paneId }, \"Pane not found, cannot send command\");\n return;\n }\n\n // tmux pane IDs (%N) are globally unique — use them directly.\n // The format \"session:%N\" is invalid because tmux interprets\n // the part after \":\" as a window name, not a pane ID.\n await execa(TMUX_BINARY, [\n \"send-keys\",\n \"-t\", info.tmuxPaneId,\n command,\n \"Enter\",\n ]);\n\n logger.debug({ paneId, command: command.slice(0, 80) }, \"Command sent to pane\");\n }\n\n /**\n * Resize a pane by tmux pane target.\n */\n async resizePane(\n paneId: string,\n dimensions: { width?: number; height?: number },\n ): Promise<void> {\n this.assertNotDisposed();\n this.assertSession();\n\n const info = this.panes.get(paneId);\n if (!info) return;\n\n const target = info.tmuxPaneId;\n\n if (dimensions.width !== undefined) {\n await execa(TMUX_BINARY, [\"resize-pane\", \"-t\", target, \"-x\", String(dimensions.width)]);\n }\n if (dimensions.height !== undefined) {\n await execa(TMUX_BINARY, [\"resize-pane\", \"-t\", target, \"-y\", String(dimensions.height)]);\n }\n }\n\n /**\n * Set the title for a tmux pane.\n */\n private async setPaneTitle(tmuxPaneId: string, title: string): Promise<void> {\n if (!this.sessionName) return;\n try {\n await execa(TMUX_BINARY, [\n \"select-pane\",\n \"-t\", tmuxPaneId,\n \"-T\", title,\n ]);\n } catch {\n logger.debug({ tmuxPaneId, title }, \"Failed to set pane title (non-fatal)\");\n }\n }\n\n /**\n * Kill a specific pane.\n */\n async killPane(paneId: string): Promise<void> {\n this.assertNotDisposed();\n\n const info = this.panes.get(paneId);\n if (!info || !this.sessionName) return;\n\n try {\n await execa(TMUX_BINARY, [\n \"kill-pane\",\n \"-t\", info.tmuxPaneId,\n ]);\n this.panes.delete(paneId);\n getEventBus().emit(\"pane:closed\", { paneId });\n logger.debug({ paneId }, \"Pane killed\");\n } catch {\n logger.debug({ paneId }, \"Failed to kill pane (may already be closed)\");\n }\n }\n\n /**\n * Attach to the tmux session (gives control to the user).\n */\n async attachSession(): Promise<void> {\n this.assertNotDisposed();\n this.assertSession();\n\n await execa(TMUX_BINARY, [\"attach-session\", \"-t\", this.sessionName!], {\n stdio: \"inherit\",\n });\n }\n\n /**\n * Check if the session still exists.\n */\n async isSessionAlive(): Promise<boolean> {\n if (!this.sessionName) return false;\n try {\n await execa(TMUX_BINARY, [\"has-session\", \"-t\", this.sessionName]);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Destroy the entire tmux session and all panes.\n */\n async destroy(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n\n if (this.sessionName) {\n await this.killSessionSilent(this.sessionName);\n\n for (const paneId of this.panes.keys()) {\n getEventBus().emit(\"pane:closed\", { paneId });\n }\n this.panes.clear();\n\n logger.info({ sessionName: this.sessionName }, \"tmux session destroyed\");\n this.sessionName = undefined;\n }\n }\n\n /**\n * Get the current session name.\n */\n getSessionName(): string | undefined {\n return this.sessionName;\n }\n\n /**\n * Get all tracked pane info.\n */\n getPanes(): ReadonlyMap<string, ITmuxPaneInfo> {\n return this.panes;\n }\n\n // ── Private Helpers ─────────────────────────────────────────────────\n\n private async splitPane(geometry: IPaneGeometry): Promise<string> {\n const splitFlag = geometry.splitDirection === \"horizontal\" ? \"-h\" : \"-v\";\n\n const result = await execa(TMUX_BINARY, [\n \"split-window\",\n splitFlag,\n \"-t\", this.sessionName!,\n \"-P\",\n \"-F\", \"#{pane_id}\",\n ]);\n\n const tmuxPaneId = result.stdout.trim();\n logger.debug(\n { tmuxPaneId, direction: geometry.splitDirection },\n \"Pane split created\",\n );\n return tmuxPaneId;\n }\n\n private async getFirstPaneId(): Promise<string> {\n const result = await execa(TMUX_BINARY, [\n \"list-panes\",\n \"-t\", this.sessionName!,\n \"-F\", \"#{pane_id}\",\n ]);\n const firstLine = result.stdout.trim().split(\"\\n\")[0];\n return firstLine ?? \"%0\";\n }\n\n private async equalizeLayout(): Promise<void> {\n if (!this.sessionName) return;\n try {\n await execa(TMUX_BINARY, [\n \"select-layout\",\n \"-t\", this.sessionName,\n \"tiled\",\n ]);\n } catch {\n logger.debug(\"Failed to equalize layout (non-fatal)\");\n }\n }\n\n private async killSessionSilent(sessionName: string): Promise<void> {\n try {\n await execa(TMUX_BINARY, [\"kill-session\", \"-t\", sessionName]);\n } catch {\n // Session may not exist — ignore\n }\n }\n\n private assertNotDisposed(): void {\n if (this.disposed) {\n throw new AgentSpawnError(\"tmux\", \"TmuxManager has been disposed\");\n }\n }\n\n private assertSession(): void {\n if (!this.sessionName) {\n throw new AgentSpawnError(\"tmux\", \"No active tmux session. Call createSession() first.\");\n }\n }\n}\n"]}