@bytespell/shella 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/bin/cli.js +1166 -304
  2. package/dist/bin/cli.js.map +1 -1
  3. package/dist/index.js +1166 -304
  4. package/dist/index.js.map +1 -1
  5. package/dist/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-EGFlDlSf.js} +1 -1
  6. package/{public/assets/arc-CCivY36l.js → dist/public/assets/arc-BmFavzWc.js} +1 -1
  7. package/{public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → dist/public/assets/architectureDiagram-VXUJARFQ-CZnDNYtj.js} +1 -1
  8. package/{public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → dist/public/assets/blockDiagram-VD42YOAC-CabGM_NX.js} +1 -1
  9. package/dist/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-BTq0tfrY.js} +1 -1
  10. package/dist/public/assets/channel-CNSk0Rnk.js +1 -0
  11. package/dist/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DP2HLRAj.js} +1 -1
  12. package/dist/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-_ByWPNnF.js} +1 -1
  13. package/{public/assets/chunk-B4BG7PRW-DU8Wql9S.js → dist/public/assets/chunk-B4BG7PRW-BkPl9d2w.js} +1 -1
  14. package/{public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → dist/public/assets/chunk-DI55MBZ5-Bx6nm2tL.js} +1 -1
  15. package/dist/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-DZpP7ydN.js} +1 -1
  16. package/dist/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DSX4eX_h.js} +1 -1
  17. package/dist/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-1uzZkVFg.js} +1 -1
  18. package/dist/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-BwKElFeo.js} +1 -1
  19. package/dist/public/assets/classDiagram-2ON5EDUG-CNctsmkW.js +1 -0
  20. package/dist/public/assets/classDiagram-v2-WZHVMYZB-CNctsmkW.js +1 -0
  21. package/dist/public/assets/clone-CUs-4v14.js +1 -0
  22. package/dist/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-BoXZl_RQ.js} +1 -1
  23. package/dist/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-Dux9YKGp.js} +1 -1
  24. package/dist/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-Bcdq9Aj5.js} +1 -1
  25. package/dist/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-CA-sK31I.js} +1 -1
  26. package/dist/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-De47GBMW.js} +1 -1
  27. package/dist/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-DJm2AaGk.js} +1 -1
  28. package/{public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → dist/public/assets/erDiagram-Q2GNP2WA-Lu5P0RSi.js} +1 -1
  29. package/{public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → dist/public/assets/flowDiagram-NV44I4VS-DflY4Ld4.js} +1 -1
  30. package/dist/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-B8jvAfhh.js} +1 -1
  31. package/dist/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-B5ATDGVE.js} +1 -1
  32. package/dist/public/assets/{graph-CtTAhETf.js → graph-8Q4ZN1_7.js} +1 -1
  33. package/dist/public/assets/index-B-jh1L5n.css +1 -0
  34. package/dist/public/assets/index-BEOKXaJG.js +23 -0
  35. package/dist/public/assets/index-DRWhjxj6.js +1781 -0
  36. package/dist/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-qrN7AH1w.js} +1 -1
  37. package/dist/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-CnL6w_Jf.js} +1 -1
  38. package/{public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → dist/public/assets/kanban-definition-3W4ZIXB7-tuy_lNx5.js} +1 -1
  39. package/dist/public/assets/{layout-DolajoeL.js → layout-BCBlc1xg.js} +1 -1
  40. package/dist/public/assets/{linear-B5Hf7uIN.js → linear-BzyanBSW.js} +1 -1
  41. package/dist/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-CKdMcV_T.js} +5 -5
  42. package/dist/public/assets/{min-UFvm8GLY.js → min-CQR8eUcq.js} +1 -1
  43. package/dist/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-fQec8JxD.js} +1 -1
  44. package/dist/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BrLsJv83.js} +1 -1
  45. package/{public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → dist/public/assets/quadrantDiagram-AYHSOK5B-BPHHSjXt.js} +1 -1
  46. package/dist/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-Bb93lhEQ.js} +1 -1
  47. package/{public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → dist/public/assets/sankeyDiagram-TZEHDZUN-CDK30tx7.js} +1 -1
  48. package/dist/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-B2WQiW_k.js} +1 -1
  49. package/{public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → dist/public/assets/stateDiagram-FKZM4ZOC-uM1w_xjr.js} +1 -1
  50. package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BGfXuXW9.js +1 -0
  51. package/{public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → dist/public/assets/timeline-definition-IT6M3QCI-07DImEJ7.js} +1 -1
  52. package/dist/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-N0jjjXe0.js} +1 -1
  53. package/{public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → dist/public/assets/xychartDiagram-PRI3JC2R-BV-5VcHE.js} +1 -1
  54. package/dist/public/index.html +15 -2
  55. package/package.json +6 -2
  56. package/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-EGFlDlSf.js} +1 -1
  57. package/{dist/public/assets/arc-CCivY36l.js → public/assets/arc-BmFavzWc.js} +1 -1
  58. package/{dist/public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → public/assets/architectureDiagram-VXUJARFQ-CZnDNYtj.js} +1 -1
  59. package/{dist/public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → public/assets/blockDiagram-VD42YOAC-CabGM_NX.js} +1 -1
  60. package/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-BTq0tfrY.js} +1 -1
  61. package/public/assets/channel-CNSk0Rnk.js +1 -0
  62. package/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DP2HLRAj.js} +1 -1
  63. package/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-_ByWPNnF.js} +1 -1
  64. package/{dist/public/assets/chunk-B4BG7PRW-DU8Wql9S.js → public/assets/chunk-B4BG7PRW-BkPl9d2w.js} +1 -1
  65. package/{dist/public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → public/assets/chunk-DI55MBZ5-Bx6nm2tL.js} +1 -1
  66. package/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-DZpP7ydN.js} +1 -1
  67. package/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DSX4eX_h.js} +1 -1
  68. package/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-1uzZkVFg.js} +1 -1
  69. package/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-BwKElFeo.js} +1 -1
  70. package/public/assets/classDiagram-2ON5EDUG-CNctsmkW.js +1 -0
  71. package/public/assets/classDiagram-v2-WZHVMYZB-CNctsmkW.js +1 -0
  72. package/public/assets/clone-CUs-4v14.js +1 -0
  73. package/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-BoXZl_RQ.js} +1 -1
  74. package/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-Dux9YKGp.js} +1 -1
  75. package/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-Bcdq9Aj5.js} +1 -1
  76. package/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-CA-sK31I.js} +1 -1
  77. package/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-De47GBMW.js} +1 -1
  78. package/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-DJm2AaGk.js} +1 -1
  79. package/{dist/public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → public/assets/erDiagram-Q2GNP2WA-Lu5P0RSi.js} +1 -1
  80. package/{dist/public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → public/assets/flowDiagram-NV44I4VS-DflY4Ld4.js} +1 -1
  81. package/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-B8jvAfhh.js} +1 -1
  82. package/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-B5ATDGVE.js} +1 -1
  83. package/public/assets/{graph-CtTAhETf.js → graph-8Q4ZN1_7.js} +1 -1
  84. package/public/assets/index-B-jh1L5n.css +1 -0
  85. package/public/assets/index-BEOKXaJG.js +23 -0
  86. package/public/assets/index-DRWhjxj6.js +1781 -0
  87. package/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-qrN7AH1w.js} +1 -1
  88. package/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-CnL6w_Jf.js} +1 -1
  89. package/{dist/public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → public/assets/kanban-definition-3W4ZIXB7-tuy_lNx5.js} +1 -1
  90. package/public/assets/{layout-DolajoeL.js → layout-BCBlc1xg.js} +1 -1
  91. package/public/assets/{linear-B5Hf7uIN.js → linear-BzyanBSW.js} +1 -1
  92. package/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-CKdMcV_T.js} +5 -5
  93. package/public/assets/{min-UFvm8GLY.js → min-CQR8eUcq.js} +1 -1
  94. package/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-fQec8JxD.js} +1 -1
  95. package/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BrLsJv83.js} +1 -1
  96. package/{dist/public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → public/assets/quadrantDiagram-AYHSOK5B-BPHHSjXt.js} +1 -1
  97. package/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-Bb93lhEQ.js} +1 -1
  98. package/{dist/public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → public/assets/sankeyDiagram-TZEHDZUN-CDK30tx7.js} +1 -1
  99. package/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-B2WQiW_k.js} +1 -1
  100. package/{dist/public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → public/assets/stateDiagram-FKZM4ZOC-uM1w_xjr.js} +1 -1
  101. package/public/assets/stateDiagram-v2-4FDKWEC3-BGfXuXW9.js +1 -0
  102. package/{dist/public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → public/assets/timeline-definition-IT6M3QCI-07DImEJ7.js} +1 -1
  103. package/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-N0jjjXe0.js} +1 -1
  104. package/{dist/public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → public/assets/xychartDiagram-PRI3JC2R-BV-5VcHE.js} +1 -1
  105. package/public/index.html +15 -2
  106. package/dist/public/assets/channel-CgkYY8Ci.js +0 -1
  107. package/dist/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
  108. package/dist/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
  109. package/dist/public/assets/clone-mkrd6lC9.js +0 -1
  110. package/dist/public/assets/index-Czr2q0NP.css +0 -1
  111. package/dist/public/assets/index-kEIJA0pC.js +0 -1781
  112. package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +0 -1
  113. package/public/assets/channel-CgkYY8Ci.js +0 -1
  114. package/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
  115. package/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
  116. package/public/assets/clone-mkrd6lC9.js +0 -1
  117. package/public/assets/index-Czr2q0NP.css +0 -1
  118. package/public/assets/index-kEIJA0pC.js +0 -1781
  119. package/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +0 -1
package/dist/bin/cli.js CHANGED
@@ -15,13 +15,12 @@ import * as p from "@clack/prompts";
15
15
  import express from "express";
16
16
  import { createServer } from "http";
17
17
  import { WebSocketServer } from "ws";
18
- import path2 from "path";
19
- import { fileURLToPath } from "url";
18
+ import path8 from "path";
19
+ import { fileURLToPath as fileURLToPath2 } from "url";
20
20
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
21
21
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
22
- import { agentManager as agentManager2 } from "@bytespell/amux/agents/manager";
22
+ import { muxManager as muxManager3 } from "@bytespell/amux/streams/manager";
23
23
  import { setVerbose, debug } from "@bytespell/amux/lib/logger";
24
- import "@bytespell/amux/db";
25
24
 
26
25
  // src/db/index.ts
27
26
  import Database from "better-sqlite3";
@@ -63,11 +62,37 @@ function ensureDir(dir) {
63
62
  var schema_exports = {};
64
63
  __export(schema_exports, {
65
64
  panes: () => panes,
65
+ streamConfigs: () => streamConfigs,
66
+ streams: () => streams,
66
67
  uiState: () => uiState,
67
68
  windows: () => windows
68
69
  });
69
70
  import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
70
71
  import { sql } from "drizzle-orm";
72
+ var streamConfigs = sqliteTable("stream_configs", {
73
+ id: text("id").primaryKey(),
74
+ name: text("name").notNull(),
75
+ /** Driver type for routing (e.g., 'acp', 'pty', or custom plugin types) */
76
+ driverType: text("driver_type").notNull().default("acp"),
77
+ command: text("command").notNull(),
78
+ args: text("args", { mode: "json" }).$type().default([]),
79
+ env: text("env", { mode: "json" }).$type().default({}),
80
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
81
+ });
82
+ var streams = sqliteTable("streams", {
83
+ id: text("id").primaryKey(),
84
+ directory: text("directory").notNull(),
85
+ streamConfigId: text("stream_config_id").notNull().references(() => streamConfigs.id),
86
+ /** Stream type determined by the driver at creation time. Built-in: 'acp', 'pty' */
87
+ streamType: text("stream_type").notNull(),
88
+ /** Generic driver state (e.g., ACP's session ID for resumption) */
89
+ driverState: text("driver_state", { mode: "json" }).$type(),
90
+ /** Title from stream (via session_info_update for ACP) */
91
+ title: text("title"),
92
+ model: text("model"),
93
+ mode: text("mode"),
94
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
95
+ });
71
96
  var windows = sqliteTable("windows", {
72
97
  id: text("id").primaryKey(),
73
98
  title: text("title").notNull(),
@@ -81,8 +106,12 @@ var windows = sqliteTable("windows", {
81
106
  var panes = sqliteTable("panes", {
82
107
  id: text("id").primaryKey(),
83
108
  windowId: text("window_id").notNull().references(() => windows.id, { onDelete: "cascade" }),
84
- /** Content configuration. Null when pane type not yet selected. */
109
+ /** Static content (browser URL, etc.). Null when no content. */
85
110
  content: text("content", { mode: "json" }).$type(),
111
+ /** Live event stream (amux stream). Null when no stream. */
112
+ eventStream: text("event_stream", { mode: "json" }).$type(),
113
+ /** Renderer ID (e.g., 'builtin:terminal', 'project:custom-viewer'). Null = use default. */
114
+ rendererId: text("renderer_id"),
86
115
  createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
87
116
  });
88
117
  var uiState = sqliteTable("ui_state", {
@@ -97,6 +126,30 @@ sqlite.pragma("journal_mode = WAL");
97
126
  sqlite.pragma("foreign_keys = ON");
98
127
  var db = drizzle(sqlite, { schema: schema_exports });
99
128
  sqlite.exec(`
129
+ -- Stream tables (from amux)
130
+ CREATE TABLE IF NOT EXISTS stream_configs (
131
+ id TEXT PRIMARY KEY,
132
+ name TEXT NOT NULL,
133
+ driver_type TEXT NOT NULL DEFAULT 'acp',
134
+ command TEXT NOT NULL,
135
+ args TEXT DEFAULT '[]',
136
+ env TEXT DEFAULT '{}',
137
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
138
+ );
139
+
140
+ CREATE TABLE IF NOT EXISTS streams (
141
+ id TEXT PRIMARY KEY,
142
+ directory TEXT NOT NULL,
143
+ stream_config_id TEXT NOT NULL REFERENCES stream_configs(id),
144
+ stream_type TEXT NOT NULL,
145
+ driver_state TEXT,
146
+ title TEXT,
147
+ model TEXT,
148
+ mode TEXT,
149
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
150
+ );
151
+
152
+ -- UI tables
100
153
  CREATE TABLE IF NOT EXISTS windows (
101
154
  id TEXT PRIMARY KEY,
102
155
  title TEXT NOT NULL,
@@ -111,6 +164,8 @@ sqlite.exec(`
111
164
  id TEXT PRIMARY KEY,
112
165
  window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
113
166
  content TEXT,
167
+ event_stream TEXT,
168
+ renderer_id TEXT,
114
169
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
115
170
  );
116
171
 
@@ -120,157 +175,686 @@ sqlite.exec(`
120
175
  );
121
176
  `);
122
177
  try {
123
- const tableInfo = sqlite.prepare("PRAGMA table_info(panes)").all();
124
- const hasSessionId = tableInfo.some((col) => col.name === "session_id");
125
- const hasContent = tableInfo.some((col) => col.name === "content");
126
- if (hasSessionId && !hasContent) {
127
- sqlite.exec(`
128
- -- Disable foreign keys temporarily
129
- PRAGMA foreign_keys = OFF;
178
+ const configInfo = sqlite.prepare(`PRAGMA table_info(stream_configs)`).all();
179
+ const hasDriverType = configInfo.some((col) => col.name === "driver_type");
180
+ if (!hasDriverType) {
181
+ console.log("[db] Adding driver_type column to stream_configs...");
182
+ sqlite.exec(`ALTER TABLE stream_configs ADD COLUMN driver_type TEXT NOT NULL DEFAULT 'acp'`);
183
+ sqlite.exec(`UPDATE stream_configs SET driver_type = 'pty' WHERE command = '__shell__'`);
184
+ sqlite.exec(`UPDATE stream_configs SET driver_type = 'mock' WHERE command = '__mock__'`);
185
+ console.log("[db] driver_type migration complete");
186
+ }
187
+ } catch (e) {
188
+ }
189
+ try {
190
+ const tableInfo = sqlite.prepare(`PRAGMA table_info(panes)`).all();
191
+ const hasProcessColumn = tableInfo.some((col) => col.name === "process");
192
+ const hasEventStreamColumn = tableInfo.some((col) => col.name === "event_stream");
193
+ if (hasProcessColumn && !hasEventStreamColumn) {
194
+ console.log("[db] Migrating panes.process to panes.event_stream...");
195
+ sqlite.exec(`ALTER TABLE panes ADD COLUMN event_stream TEXT`);
196
+ const panesWithProcess = sqlite.prepare(`SELECT id, process FROM panes WHERE process IS NOT NULL`).all();
197
+ for (const pane of panesWithProcess) {
198
+ try {
199
+ const process2 = JSON.parse(pane.process);
200
+ if (process2.type === "session" && process2.sessionId) {
201
+ const eventStream = JSON.stringify({ type: "acp", streamId: process2.sessionId });
202
+ sqlite.prepare(`UPDATE panes SET event_stream = ? WHERE id = ?`).run(eventStream, pane.id);
203
+ }
204
+ } catch (e) {
205
+ }
206
+ }
207
+ console.log("[db] Migration complete, migrated", panesWithProcess.length, "panes");
208
+ }
209
+ } catch (e) {
210
+ }
130
211
 
131
- -- Create new table with content column
132
- CREATE TABLE panes_new (
133
- id TEXT PRIMARY KEY,
134
- window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
135
- content TEXT,
136
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
137
- );
212
+ // src/lib/configDiscovery.ts
213
+ import { execSync } from "child_process";
214
+ import { randomUUID } from "crypto";
215
+ function commandExists(cmd) {
216
+ try {
217
+ execSync(`which ${cmd}`, { stdio: "ignore" });
218
+ return true;
219
+ } catch {
220
+ return false;
221
+ }
222
+ }
223
+ var claudeDiscoverer = {
224
+ name: "claude",
225
+ discover() {
226
+ if (!commandExists("claude")) {
227
+ return null;
228
+ }
229
+ return {
230
+ id: randomUUID(),
231
+ name: "claude",
232
+ driverType: "acp",
233
+ command: "npx",
234
+ args: ["@zed-industries/claude-code-acp"],
235
+ env: {},
236
+ createdAt: /* @__PURE__ */ new Date()
237
+ };
238
+ }
239
+ };
240
+ var codexDiscoverer = {
241
+ name: "codex",
242
+ discover() {
243
+ if (!commandExists("codex")) {
244
+ return null;
245
+ }
246
+ return {
247
+ id: randomUUID(),
248
+ name: "codex",
249
+ driverType: "acp",
250
+ command: "npx",
251
+ args: ["@zed-industries/codex-acp"],
252
+ env: {},
253
+ createdAt: /* @__PURE__ */ new Date()
254
+ };
255
+ }
256
+ };
257
+ var geminiDiscoverer = {
258
+ name: "gemini",
259
+ discover() {
260
+ if (!commandExists("gemini")) {
261
+ return null;
262
+ }
263
+ return {
264
+ id: randomUUID(),
265
+ name: "gemini",
266
+ driverType: "acp",
267
+ command: "gemini",
268
+ args: ["--experimental-acp"],
269
+ env: {},
270
+ createdAt: /* @__PURE__ */ new Date()
271
+ };
272
+ }
273
+ };
274
+ var openCodeDiscoverer = {
275
+ name: "opencode",
276
+ discover() {
277
+ if (!commandExists("opencode")) {
278
+ return null;
279
+ }
280
+ return {
281
+ id: randomUUID(),
282
+ name: "opencode",
283
+ driverType: "acp",
284
+ command: "opencode",
285
+ args: ["acp"],
286
+ env: {},
287
+ createdAt: /* @__PURE__ */ new Date()
288
+ };
289
+ }
290
+ };
291
+ var shellDiscoverer = {
292
+ name: "Shell",
293
+ discover() {
294
+ return {
295
+ id: "shell",
296
+ name: "shell",
297
+ driverType: "pty",
298
+ command: "",
299
+ // PTY driver ignores command, spawns user's $SHELL
300
+ args: [],
301
+ env: {},
302
+ createdAt: /* @__PURE__ */ new Date()
303
+ };
304
+ }
305
+ };
306
+ var builtInDiscoverers = [
307
+ claudeDiscoverer,
308
+ codexDiscoverer,
309
+ geminiDiscoverer,
310
+ openCodeDiscoverer,
311
+ shellDiscoverer
312
+ ];
313
+ function discoverAndRegisterConfigs(discoverers) {
314
+ const existing = db.select().from(streamConfigs).all();
315
+ let added = 0;
316
+ for (const discoverer of discoverers) {
317
+ const config = discoverer.discover();
318
+ if (!config) {
319
+ continue;
320
+ }
321
+ const alreadyExists = existing.some(
322
+ (e) => e.command === config.command && JSON.stringify(e.args) === JSON.stringify(config.args)
323
+ );
324
+ if (!alreadyExists) {
325
+ db.insert(streamConfigs).values({
326
+ id: config.id,
327
+ name: config.name,
328
+ driverType: config.driverType,
329
+ command: config.command,
330
+ args: config.args,
331
+ env: config.env,
332
+ createdAt: config.createdAt
333
+ }).run();
334
+ console.log(`[config] Discovered: ${config.name}`);
335
+ added++;
336
+ }
337
+ }
338
+ if (added > 0) {
339
+ console.log(`[config] ${added} new stream config(s) registered`);
340
+ }
341
+ }
138
342
 
139
- -- Copy data, converting session_id to content JSON
140
- INSERT INTO panes_new (id, window_id, content, created_at)
141
- SELECT
142
- id,
143
- window_id,
144
- CASE
145
- WHEN session_id IS NOT NULL THEN json_object('type', 'session', 'sessionId', session_id)
146
- ELSE NULL
147
- END,
148
- created_at
149
- FROM panes;
343
+ // src/lib/driverWatcher.ts
344
+ import chokidar from "chokidar";
345
+ import * as path3 from "path";
346
+ import * as os2 from "os";
347
+ import * as fs3 from "fs/promises";
348
+ import { EventEmitter } from "events";
349
+
350
+ // src/lib/driverCompiler.ts
351
+ import { build } from "esbuild";
352
+ import * as fs2 from "fs/promises";
353
+ import * as path2 from "path";
354
+ import { fileURLToPath } from "url";
355
+ var __dirname = path2.dirname(fileURLToPath(import.meta.url));
356
+ async function compileDriver(pluginDir) {
357
+ const serverPath = path2.join(pluginDir, "server.ts");
358
+ try {
359
+ await fs2.access(serverPath);
360
+ } catch {
361
+ return null;
362
+ }
363
+ const metaPath = path2.join(pluginDir, "meta.json");
364
+ let meta;
365
+ try {
366
+ const metaContent = await fs2.readFile(metaPath, "utf-8");
367
+ meta = JSON.parse(metaContent);
368
+ } catch (err) {
369
+ throw new Error(
370
+ `Failed to read meta.json for ${pluginDir}: ${err instanceof Error ? err.message : String(err)}`
371
+ );
372
+ }
373
+ if (!meta.id || typeof meta.id !== "string") {
374
+ throw new Error(`Plugin in ${pluginDir}: meta.json requires "id" field`);
375
+ }
376
+ if (!meta.name || typeof meta.name !== "string") {
377
+ throw new Error(`Plugin in ${pluginDir}: meta.json requires "name" field`);
378
+ }
379
+ const outDir = path2.join(pluginDir, ".shella");
380
+ const outFile = path2.join(outDir, "server.mjs");
381
+ await fs2.mkdir(outDir, { recursive: true });
382
+ const pkgPath = path2.join(pluginDir, "package.json");
383
+ let hasPackageJson = false;
384
+ try {
385
+ await fs2.access(pkgPath);
386
+ hasPackageJson = true;
387
+ } catch {
388
+ }
389
+ const shellaRoot = path2.resolve(__dirname, "../..");
390
+ const sdkPath = path2.resolve(shellaRoot, "../sdk/dist");
391
+ await build({
392
+ entryPoints: [serverPath],
393
+ bundle: true,
394
+ write: true,
395
+ format: "esm",
396
+ platform: "node",
397
+ target: "node20",
398
+ outfile: outFile,
399
+ alias: {
400
+ "@bytespell/shella-sdk": path2.join(sdkPath, "index.js"),
401
+ "@bytespell/shella-sdk/server": path2.join(sdkPath, "server.js")
402
+ },
403
+ // If plugin has package.json, look for node_modules
404
+ nodePaths: hasPackageJson ? [path2.join(pluginDir, "node_modules")] : [],
405
+ minify: false
406
+ });
407
+ return {
408
+ id: meta.id,
409
+ name: meta.name,
410
+ filePath: outFile,
411
+ pluginDir
412
+ };
413
+ }
150
414
 
151
- -- Drop old table and rename new
152
- DROP TABLE panes;
153
- ALTER TABLE panes_new RENAME TO panes;
415
+ // src/lib/driverWatcher.ts
416
+ import { muxManager } from "@bytespell/amux/streams/manager";
417
+ import { eq } from "drizzle-orm";
418
+ var DriverEventEmitter = class extends EventEmitter {
419
+ emit(event, data) {
420
+ return super.emit(event, data);
421
+ }
422
+ on(event, listener) {
423
+ return super.on(event, listener);
424
+ }
425
+ off(event, listener) {
426
+ return super.off(event, listener);
427
+ }
428
+ };
429
+ var loadedDrivers = /* @__PURE__ */ new Map();
430
+ async function loadDriver(compiled) {
431
+ const entry = loadedDrivers.get(compiled.pluginDir);
432
+ const version = (entry?.version ?? 0) + 1;
433
+ const url = `file://${compiled.filePath}?v=${version}`;
434
+ const module = await import(
435
+ /* @vite-ignore */
436
+ url
437
+ );
438
+ const driver = module.default;
439
+ if (!driver || typeof driver.start !== "function") {
440
+ throw new Error(`Plugin driver must export default with start() method`);
441
+ }
442
+ driver.type = compiled.id;
443
+ loadedDrivers.set(compiled.pluginDir, { id: compiled.id, version });
444
+ return driver;
445
+ }
446
+ function ensureStreamConfig(compiled) {
447
+ const existing = db.select().from(streamConfigs).where(eq(streamConfigs.id, compiled.id)).get();
448
+ if (!existing) {
449
+ db.insert(streamConfigs).values({
450
+ id: compiled.id,
451
+ name: compiled.name,
452
+ driverType: compiled.id,
453
+ // Driver type matches plugin id
454
+ command: "",
455
+ // Plugin drivers don't spawn processes
456
+ args: [],
457
+ env: {},
458
+ createdAt: /* @__PURE__ */ new Date()
459
+ }).run();
460
+ } else if (existing.name !== compiled.name) {
461
+ db.update(streamConfigs).set({ name: compiled.name }).where(eq(streamConfigs.id, compiled.id)).run();
462
+ }
463
+ }
464
+ function removeStreamConfig(id) {
465
+ db.delete(streamConfigs).where(eq(streamConfigs.id, id)).run();
466
+ }
467
+ function watchDrivers(emitter) {
468
+ const userPath = path3.join(os2.homedir(), ".shella", "renderers");
469
+ const pendingCompiles = /* @__PURE__ */ new Map();
470
+ const watcher = chokidar.watch(userPath, {
471
+ ignoreInitial: false,
472
+ persistent: true,
473
+ awaitWriteFinish: {
474
+ stabilityThreshold: 150,
475
+ pollInterval: 50
476
+ },
477
+ depth: 3
478
+ // Watch inside plugin folders
479
+ });
480
+ const getPluginDir = (filePath) => {
481
+ const relative3 = path3.relative(userPath, filePath);
482
+ const parts = relative3.split(path3.sep);
483
+ if (parts.length >= 1 && parts[0]) {
484
+ return path3.join(userPath, parts[0]);
485
+ }
486
+ return null;
487
+ };
488
+ const isServerFile = (filePath) => {
489
+ const relative3 = path3.relative(userPath, filePath);
490
+ const parts = relative3.split(path3.sep);
491
+ return parts.length >= 2 && (parts[1] === "server.ts" || parts[1]?.startsWith("server/") || filePath.includes("/server/"));
492
+ };
493
+ const scheduleCompile = (pluginDir) => {
494
+ if (pendingCompiles.has(pluginDir)) {
495
+ clearTimeout(pendingCompiles.get(pluginDir));
496
+ }
497
+ pendingCompiles.set(
498
+ pluginDir,
499
+ setTimeout(async () => {
500
+ pendingCompiles.delete(pluginDir);
501
+ await handleDriverChange(pluginDir);
502
+ }, 150)
503
+ );
504
+ };
505
+ const handleDriverChange = async (pluginDir) => {
506
+ try {
507
+ const compiled = await compileDriver(pluginDir);
508
+ if (!compiled) return;
509
+ const existing = loadedDrivers.get(pluginDir);
510
+ if (existing) {
511
+ muxManager.unregisterDriver(existing.id);
512
+ }
513
+ const driver = await loadDriver(compiled);
514
+ muxManager.registerDriver(driver);
515
+ ensureStreamConfig(compiled);
516
+ if (!existing) {
517
+ console.log(`[drivers] Added: ${compiled.id} (${compiled.name})`);
518
+ emitter.emit("driver:added", { id: compiled.id, name: compiled.name });
519
+ } else {
520
+ console.log(`[drivers] Updated: ${compiled.id} (${compiled.name})`);
521
+ emitter.emit("driver:updated", { id: compiled.id, name: compiled.name });
522
+ }
523
+ } catch (err) {
524
+ console.error(`[drivers] Error compiling ${pluginDir}:`, err);
525
+ emitter.emit("driver:error", {
526
+ pluginDir,
527
+ error: err instanceof Error ? err : new Error(String(err))
528
+ });
529
+ }
530
+ };
531
+ watcher.on("add", (filePath) => {
532
+ if (isServerFile(filePath)) {
533
+ const pluginDir = getPluginDir(filePath);
534
+ if (pluginDir) scheduleCompile(pluginDir);
535
+ }
536
+ });
537
+ watcher.on("change", (filePath) => {
538
+ if (isServerFile(filePath)) {
539
+ const pluginDir = getPluginDir(filePath);
540
+ if (pluginDir) scheduleCompile(pluginDir);
541
+ }
542
+ });
543
+ watcher.on("unlink", (filePath) => {
544
+ if (filePath.endsWith("server.ts")) {
545
+ const pluginDir = getPluginDir(filePath);
546
+ if (pluginDir) {
547
+ const entry = loadedDrivers.get(pluginDir);
548
+ if (entry) {
549
+ muxManager.unregisterDriver(entry.id);
550
+ removeStreamConfig(entry.id);
551
+ loadedDrivers.delete(pluginDir);
552
+ console.log(`[drivers] Removed: ${entry.id}`);
553
+ emitter.emit("driver:removed", { id: entry.id });
554
+ }
555
+ }
556
+ }
557
+ });
558
+ watcher.on("error", (err) => {
559
+ console.error("[drivers] Watcher error:", err);
560
+ });
561
+ return () => {
562
+ for (const timeout of pendingCompiles.values()) {
563
+ clearTimeout(timeout);
564
+ }
565
+ pendingCompiles.clear();
566
+ watcher.close();
567
+ };
568
+ }
569
+ async function scanDrivers() {
570
+ const userPath = path3.join(os2.homedir(), ".shella", "renderers");
571
+ try {
572
+ const entries = await fs3.readdir(userPath, { withFileTypes: true });
573
+ for (const entry of entries) {
574
+ if (!entry.isDirectory()) continue;
575
+ const pluginDir = path3.join(userPath, entry.name);
576
+ try {
577
+ const compiled = await compileDriver(pluginDir);
578
+ if (compiled) {
579
+ const driver = await loadDriver(compiled);
580
+ muxManager.registerDriver(driver);
581
+ ensureStreamConfig(compiled);
582
+ console.log(`[drivers] Loaded: ${compiled.id} (${compiled.name})`);
583
+ }
584
+ } catch (err) {
585
+ console.error(`[drivers] Error loading ${pluginDir}:`, err);
586
+ }
587
+ }
588
+ } catch {
589
+ }
590
+ }
591
+
592
+ // src/amuxBridge.ts
593
+ import { randomUUID as randomUUID2 } from "crypto";
594
+ import path5 from "path";
595
+ import fs5 from "fs";
596
+ import { eq as eq2, inArray } from "drizzle-orm";
154
597
 
155
- -- Re-enable foreign keys
156
- PRAGMA foreign_keys = ON;
157
- `);
158
- console.log("[shella-db] Migrated panes from session_id to content column");
598
+ // src/lib/files.ts
599
+ import * as fs4 from "fs/promises";
600
+ import * as path4 from "path";
601
+ import { glob } from "glob";
602
+ async function listFilesForAutocomplete(workingDir, partialPath, limit) {
603
+ const normalized = partialPath.startsWith("./") ? partialPath.slice(2) : partialPath;
604
+ if (normalized.includes("/")) {
605
+ return listFilesPrefix(workingDir, normalized, limit);
606
+ }
607
+ return listFilesFuzzy(workingDir, normalized, limit);
608
+ }
609
+ async function listFilesPrefix(workingDir, partialPath, limit) {
610
+ const lastSlash = partialPath.lastIndexOf("/");
611
+ const dirPart = lastSlash >= 0 ? partialPath.slice(0, lastSlash) : "";
612
+ const prefix = lastSlash >= 0 ? partialPath.slice(lastSlash + 1) : partialPath;
613
+ const targetDir = path4.resolve(workingDir, dirPart);
614
+ const normalizedWorkingDir = path4.resolve(workingDir);
615
+ const normalizedTargetDir = path4.resolve(targetDir);
616
+ if (!normalizedTargetDir.startsWith(normalizedWorkingDir)) {
617
+ return [];
618
+ }
619
+ try {
620
+ const entries = await fs4.readdir(targetDir, { withFileTypes: true });
621
+ const matching = entries.filter((e) => !e.name.startsWith(".")).filter((e) => prefix === "" || e.name.toLowerCase().startsWith(prefix.toLowerCase())).slice(0, limit * 2).map((e) => ({
622
+ name: e.name,
623
+ path: dirPart ? `${dirPart}/${e.name}` : e.name,
624
+ isDirectory: e.isDirectory()
625
+ }));
626
+ matching.sort((a, b) => {
627
+ if (a.isDirectory !== b.isDirectory) {
628
+ return a.isDirectory ? -1 : 1;
629
+ }
630
+ return a.name.localeCompare(b.name);
631
+ });
632
+ return matching.slice(0, limit);
633
+ } catch {
634
+ return [];
635
+ }
636
+ }
637
+ async function listFilesFuzzy(workingDir, query, limit) {
638
+ if (!query.trim()) return [];
639
+ try {
640
+ const pattern = `{*${query}*,**/*${query}*}`;
641
+ const matches = await glob(pattern, {
642
+ cwd: workingDir,
643
+ nodir: false,
644
+ dot: false,
645
+ // Skip hidden files
646
+ ignore: ["**/node_modules/**", "**/.git/**"],
647
+ maxDepth: 10
648
+ });
649
+ const results = [];
650
+ for (const match of matches.slice(0, limit * 2)) {
651
+ try {
652
+ const fullPath = path4.join(workingDir, match);
653
+ const stat2 = await fs4.stat(fullPath);
654
+ results.push({
655
+ name: path4.basename(match),
656
+ path: match,
657
+ isDirectory: stat2.isDirectory()
658
+ });
659
+ } catch {
660
+ }
661
+ }
662
+ results.sort((a, b) => {
663
+ if (a.isDirectory !== b.isDirectory) {
664
+ return a.isDirectory ? -1 : 1;
665
+ }
666
+ const aDepth = a.path.split("/").length;
667
+ const bDepth = b.path.split("/").length;
668
+ if (aDepth !== bDepth) {
669
+ return aDepth - bDepth;
670
+ }
671
+ return a.path.localeCompare(b.path);
672
+ });
673
+ return results.slice(0, limit);
674
+ } catch {
675
+ return [];
159
676
  }
160
- } catch {
161
677
  }
162
678
 
163
679
  // src/amuxBridge.ts
164
- import { randomUUID } from "crypto";
165
- import { eq, inArray } from "drizzle-orm";
166
- import { db as db2, schema } from "@bytespell/amux/db";
167
- import { agentManager } from "@bytespell/amux/agents/manager";
168
- import { clearEventsForSession } from "@bytespell/amux/agents/eventStore";
169
- import { listFilesForAutocomplete } from "@bytespell/amux/trpc/files";
170
- var { sessions, agentConfigs, appState } = schema;
680
+ import { muxManager as muxManager2 } from "@bytespell/amux/streams/manager";
681
+ import { isAcpEvent } from "@bytespell/amux/types";
682
+ function getStreamStorageDir(streamId) {
683
+ return path5.join(getDataDir(), "streams", streamId);
684
+ }
685
+ function generateTitleFromMessage(message) {
686
+ const cleaned = message.replace(/@[\w./~-]+/g, "").replace(/\s+/g, " ").trim();
687
+ const firstLine = cleaned.split("\n")[0] || cleaned;
688
+ if (firstLine.length <= 50) return firstLine;
689
+ const truncated = firstLine.slice(0, 50);
690
+ const lastSpace = truncated.lastIndexOf(" ");
691
+ return lastSpace > 20 ? truncated.slice(0, lastSpace) + "..." : truncated + "...";
692
+ }
171
693
  function createAmuxBridge() {
172
- return {
173
- async getSessions(ids) {
694
+ const bridge = {
695
+ async getStreams(ids) {
174
696
  if (ids.length === 0) return [];
175
- return db2.select().from(sessions).where(inArray(sessions.id, ids)).all();
697
+ return db.select().from(streams).where(inArray(streams.id, ids)).all();
176
698
  },
177
- async getSession(id) {
178
- return db2.select().from(sessions).where(eq(sessions.id, id)).get() ?? null;
699
+ async getStream(id) {
700
+ return db.select().from(streams).where(eq2(streams.id, id)).get() ?? null;
179
701
  },
180
- async createSession(params) {
181
- let configId = params.agentConfigId;
702
+ async createStream(params) {
703
+ let configId = params.streamConfigId;
182
704
  if (!configId) {
183
- const lastUsedRow = db2.select().from(appState).where(eq(appState.key, "last_used_agent_config_id")).get();
705
+ const lastUsedRow = db.select().from(uiState).where(eq2(uiState.key, "last_used_stream_config_id")).get();
184
706
  if (lastUsedRow?.value) {
185
- const config2 = db2.select().from(agentConfigs).where(eq(agentConfigs.id, lastUsedRow.value)).get();
707
+ const config2 = db.select().from(streamConfigs).where(eq2(streamConfigs.id, lastUsedRow.value)).get();
186
708
  if (config2) configId = config2.id;
187
709
  }
188
710
  if (!configId) {
189
- const firstConfig = db2.select().from(agentConfigs).get();
711
+ const firstConfig = db.select().from(streamConfigs).get();
190
712
  if (!firstConfig) {
191
- throw new Error("No agent configs available");
713
+ throw new Error("No stream configs available");
192
714
  }
193
715
  configId = firstConfig.id;
194
716
  }
195
717
  } else {
196
- db2.insert(appState).values({ key: "last_used_agent_config_id", value: configId }).onConflictDoUpdate({ target: appState.key, set: { value: configId } }).run();
718
+ db.insert(uiState).values({ key: "last_used_stream_config_id", value: configId }).onConflictDoUpdate({ target: uiState.key, set: { value: configId } }).run();
197
719
  }
198
- const id = params.id ?? randomUUID();
720
+ const id = params.id ?? randomUUID2();
199
721
  const now = /* @__PURE__ */ new Date();
200
- const config = db2.select().from(agentConfigs).where(eq(agentConfigs.id, configId)).get();
201
- const initialTitle = config?.streamType === "pty" ? params.directory.split("/").pop() || null : null;
202
- db2.insert(sessions).values({
722
+ const config = db.select().from(streamConfigs).where(eq2(streamConfigs.id, configId)).get();
723
+ const streamType = config?.driverType === "pty" ? "pty" : "acp";
724
+ const initialTitle = streamType === "pty" ? params.directory.split("/").pop() || null : null;
725
+ db.insert(streams).values({
203
726
  id,
204
727
  directory: params.directory,
205
- agentConfigId: configId,
206
- acpSessionId: null,
728
+ streamConfigId: configId,
729
+ streamType,
730
+ driverState: null,
207
731
  title: initialTitle,
208
732
  model: null,
209
733
  mode: null,
210
734
  createdAt: now
211
735
  }).run();
212
- return db2.select().from(sessions).where(eq(sessions.id, id)).get();
736
+ return db.select().from(streams).where(eq2(streams.id, id)).get();
213
737
  },
214
- async deleteSession(id) {
215
- await agentManager.stopForSession(id);
216
- clearEventsForSession(id);
217
- db2.delete(sessions).where(eq(sessions.id, id)).run();
738
+ async deleteStream(id) {
739
+ await muxManager2.stopForStream(id);
740
+ const storageDir = getStreamStorageDir(id);
741
+ try {
742
+ if (fs5.existsSync(storageDir)) {
743
+ fs5.rmSync(storageDir, { recursive: true });
744
+ }
745
+ } catch {
746
+ }
747
+ db.delete(streams).where(eq2(streams.id, id)).run();
218
748
  },
219
- async getAgentConfigs() {
220
- return db2.select().from(agentConfigs).all();
749
+ async getStreamConfigs() {
750
+ return db.select().from(streamConfigs).all();
221
751
  },
222
- async startAgent(sessionId) {
223
- return agentManager.startForSession(sessionId);
752
+ async startStream(streamId) {
753
+ const dbStream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
754
+ if (!dbStream) throw new Error(`Stream ${streamId} not found`);
755
+ const dbConfig = db.select().from(streamConfigs).where(eq2(streamConfigs.id, dbStream.streamConfigId)).get();
756
+ if (!dbConfig) throw new Error(`Stream config ${dbStream.streamConfigId} not found`);
757
+ const storageDir = getStreamStorageDir(streamId);
758
+ const emit = (event) => {
759
+ if (isAcpEvent(event) && event.sessionUpdate === "session_info_update") {
760
+ const title = event.title;
761
+ if (title !== void 0) {
762
+ db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
763
+ }
764
+ }
765
+ };
766
+ const handle = await muxManager2.startStream({
767
+ streamId,
768
+ driverType: dbConfig.driverType,
769
+ config: {
770
+ id: dbConfig.id,
771
+ name: dbConfig.name,
772
+ command: dbConfig.command,
773
+ args: dbConfig.args ?? [],
774
+ env: dbConfig.env ?? {}
775
+ },
776
+ cwd: dbStream.directory,
777
+ restoredState: dbStream.driverState,
778
+ emit,
779
+ storageDir
780
+ });
781
+ const driverState = muxManager2.getDriverState(streamId);
782
+ if (driverState !== void 0) {
783
+ db.update(streams).set({ driverState }).where(eq2(streams.id, streamId)).run();
784
+ }
785
+ const replayData = muxManager2.getReplayData(streamId);
786
+ const replayEvents = Array.isArray(replayData) ? replayData : [];
787
+ const scrollback = typeof replayData === "string" ? replayData : muxManager2.getTerminalScrollback(streamId);
788
+ return {
789
+ acpSessionId: handle.stream.acpSessionId,
790
+ replayEvents,
791
+ scrollback,
792
+ models: handle.stream.models,
793
+ modes: handle.stream.modes
794
+ };
224
795
  },
225
- async stopAgent(sessionId) {
226
- await agentManager.stopForSession(sessionId);
796
+ async stopStream(streamId) {
797
+ const driverState = muxManager2.getDriverState(streamId);
798
+ if (driverState !== void 0) {
799
+ db.update(streams).set({ driverState }).where(eq2(streams.id, streamId)).run();
800
+ }
801
+ await muxManager2.stopForStream(streamId);
227
802
  },
228
- async prompt(sessionId, message) {
229
- await agentManager.prompt(sessionId, message);
803
+ async input(streamId, message) {
804
+ const stream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
805
+ if (!stream) throw new Error(`Stream ${streamId} not found`);
806
+ if (!stream.title) {
807
+ const title = generateTitleFromMessage(message);
808
+ if (title) {
809
+ db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
810
+ }
811
+ }
812
+ await muxManager2.input(streamId, message);
230
813
  },
231
- async cancel(sessionId) {
232
- await agentManager.cancel(sessionId);
814
+ async cancel(streamId) {
815
+ await muxManager2.cancel(streamId);
233
816
  },
234
- async setMode(sessionId, modeId) {
235
- await agentManager.setMode(sessionId, modeId);
817
+ async setMode(streamId, modeId) {
818
+ await muxManager2.setMode(streamId, modeId);
236
819
  },
237
- async setModel(sessionId, modelId) {
238
- await agentManager.setModel(sessionId, modelId);
820
+ async setModel(streamId, modelId) {
821
+ await muxManager2.setModel(streamId, modelId);
239
822
  },
240
- respondPermission(sessionId, requestId, optionId) {
241
- agentManager.respondPermission(sessionId, requestId, optionId);
823
+ respondPermission(streamId, requestId, optionId) {
824
+ muxManager2.respondPermission(streamId, requestId, optionId);
242
825
  },
243
- subscribeToSession(sessionId, callback) {
826
+ subscribeToStream(streamId, callback) {
244
827
  const handler = (event) => {
245
- if (event.sessionId === sessionId) {
246
- callback(event.update);
828
+ if (event.streamId === streamId) {
829
+ callback(event);
247
830
  }
248
831
  };
249
- agentManager.on("update", handler);
250
- return () => agentManager.off("update", handler);
832
+ muxManager2.on("update", handler);
833
+ return () => muxManager2.off("update", handler);
251
834
  },
252
- async listFilesForAutocomplete(sessionId, partialPath, limit) {
253
- const session = db2.select().from(sessions).where(eq(sessions.id, sessionId)).get();
254
- if (!session) throw new Error(`Session ${sessionId} not found`);
255
- return listFilesForAutocomplete(session.directory, partialPath, limit);
835
+ async listFilesForAutocomplete(streamId, partialPath, limit) {
836
+ const stream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
837
+ if (!stream) throw new Error(`Stream ${streamId} not found`);
838
+ return listFilesForAutocomplete(stream.directory, partialPath, limit);
256
839
  },
257
840
  // Terminal operations
258
- terminalWrite(sessionId, data) {
259
- agentManager.terminalWrite(sessionId, data);
841
+ terminalWrite(streamId, data) {
842
+ muxManager2.terminalWrite(streamId, data);
260
843
  },
261
- terminalResize(sessionId, cols, rows) {
262
- agentManager.terminalResize(sessionId, cols, rows);
844
+ terminalResize(streamId, cols, rows) {
845
+ muxManager2.terminalResize(streamId, cols, rows);
263
846
  },
264
- getTerminalScrollback(sessionId) {
265
- return agentManager.getTerminalScrollback(sessionId);
847
+ getTerminalScrollback(streamId) {
848
+ return muxManager2.getTerminalScrollback(streamId);
266
849
  },
267
- isTerminalSession(sessionId) {
268
- return agentManager.isTerminalSession(sessionId);
850
+ isTerminalStream(streamId) {
851
+ return muxManager2.isTerminalStream(streamId);
269
852
  },
270
- updateSessionTitle(sessionId, title) {
271
- db2.update(sessions).set({ title }).where(eq(sessions.id, sessionId)).run();
853
+ updateStreamTitle(streamId, title) {
854
+ db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
272
855
  }
273
856
  };
857
+ return bridge;
274
858
  }
275
859
 
276
860
  // src/trpc/trpc.ts
@@ -281,8 +865,8 @@ var publicProcedure = t.procedure;
281
865
 
282
866
  // src/trpc/windows.ts
283
867
  import { z } from "zod";
284
- import { randomUUID as randomUUID2 } from "crypto";
285
- import { eq as eq2 } from "drizzle-orm";
868
+ import { randomUUID as randomUUID3 } from "crypto";
869
+ import { eq as eq3 } from "drizzle-orm";
286
870
 
287
871
  // client/lib/layout.ts
288
872
  function createLeaf(paneId) {
@@ -297,13 +881,13 @@ function getAllPaneIds(node) {
297
881
  }
298
882
  return [...getAllPaneIds(node.first), ...getAllPaneIds(node.second)];
299
883
  }
300
- function findPanePath(node, paneId, path3 = []) {
884
+ function findPanePath(node, paneId, path9 = []) {
301
885
  if (node.type === "leaf") {
302
- return node.paneId === paneId ? path3 : null;
886
+ return node.paneId === paneId ? path9 : null;
303
887
  }
304
- const firstPath = findPanePath(node.first, paneId, [...path3, "first"]);
888
+ const firstPath = findPanePath(node.first, paneId, [...path9, "first"]);
305
889
  if (firstPath) return firstPath;
306
- return findPanePath(node.second, paneId, [...path3, "second"]);
890
+ return findPanePath(node.second, paneId, [...path9, "second"]);
307
891
  }
308
892
  function replacePaneWithNode(root, paneId, newNode) {
309
893
  if (root.type === "leaf") {
@@ -340,15 +924,15 @@ function removePane(root, paneId) {
340
924
  }
341
925
  return root;
342
926
  }
343
- function updateRatio(root, path3, newRatio) {
344
- if (path3.length === 0) {
927
+ function updateRatio(root, path9, newRatio) {
928
+ if (path9.length === 0) {
345
929
  if (root.type === "branch") {
346
930
  return { ...root, ratio: Math.max(10, Math.min(90, newRatio)) };
347
931
  }
348
932
  return root;
349
933
  }
350
934
  if (root.type === "leaf") return root;
351
- const [next, ...rest] = path3;
935
+ const [next, ...rest] = path9;
352
936
  if (next === "first") {
353
937
  return { ...root, first: updateRatio(root.first, rest, newRatio) };
354
938
  } else {
@@ -356,11 +940,11 @@ function updateRatio(root, path3, newRatio) {
356
940
  }
357
941
  }
358
942
  function getAdjacentPane(root, currentPaneId, direction) {
359
- const path3 = findPanePath(root, currentPaneId);
360
- if (!path3) return null;
943
+ const path9 = findPanePath(root, currentPaneId);
944
+ if (!path9) return null;
361
945
  let node = root;
362
946
  const pathNodes = [];
363
- for (const step of path3) {
947
+ for (const step of path9) {
364
948
  if (node.type === "branch") {
365
949
  pathNodes.push({ node, childSide: step });
366
950
  node = step === "first" ? node.first : node.second;
@@ -406,71 +990,76 @@ function generateTitle(existingCount, directory) {
406
990
  }
407
991
  var windowsRouter = router({
408
992
  /**
409
- * List all windows with their panes and sessions.
993
+ * List all windows with their panes and streams.
410
994
  */
411
995
  list: publicProcedure.query(async () => {
412
996
  const allWindows = db.select().from(windows).orderBy(windows.createdAt).all();
413
997
  const allPanes = db.select().from(panes).all();
414
- const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
415
- const sessionIds = [...new Set(
416
- allPanes.map((p2) => p2.content?.type === "session" ? p2.content.sessionId : null).filter((id) => id !== null)
998
+ const activeRow = db.select().from(uiState).where(eq3(uiState.key, "active_window_id")).get();
999
+ const streamIds = [...new Set(
1000
+ allPanes.map((p2) => p2.eventStream?.streamId ?? null).filter((id) => id !== null)
417
1001
  )];
418
- const sessionsData = await amux.getSessions(sessionIds);
419
- const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
420
- const agentConfigs2 = await amux.getAgentConfigs();
1002
+ const streamsData = await amux.getStreams(streamIds);
1003
+ const streamMap = new Map(streamsData.map((s) => [s.id, s]));
1004
+ const streamConfigs2 = await amux.getStreamConfigs();
421
1005
  return {
422
1006
  windows: allWindows.map((w) => ({
423
1007
  ...w,
424
1008
  panes: allPanes.filter((p2) => p2.windowId === w.id).map((p2) => ({
425
1009
  ...p2,
426
- // Enrich session-type content with full session data
427
- session: p2.content?.type === "session" ? sessionMap.get(p2.content.sessionId) ?? null : null
1010
+ // Enrich eventStream with full stream data
1011
+ stream: p2.eventStream ? streamMap.get(p2.eventStream.streamId) ?? null : null
428
1012
  }))
429
1013
  })),
430
1014
  activeWindowId: activeRow?.value ?? allWindows[allWindows.length - 1]?.id ?? null,
431
- agentConfigs: agentConfigs2
1015
+ streamConfigs: streamConfigs2,
1016
+ // Legacy alias
1017
+ agentConfigs: streamConfigs2
432
1018
  };
433
1019
  }),
434
1020
  /**
435
1021
  * Get a single window by ID.
436
1022
  */
437
1023
  get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
438
- const window = db.select().from(windows).where(eq2(windows.id, input.id)).get();
1024
+ const window = db.select().from(windows).where(eq3(windows.id, input.id)).get();
439
1025
  if (!window) {
440
1026
  throw new Error(`Window ${input.id} not found`);
441
1027
  }
442
- const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
443
- const sessionIds = windowPanes.map((p2) => p2.content?.type === "session" ? p2.content.sessionId : null).filter((id) => id !== null);
444
- const sessionsData = await amux.getSessions(sessionIds);
445
- const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
1028
+ const windowPanes = db.select().from(panes).where(eq3(panes.windowId, input.id)).all();
1029
+ const streamIds = windowPanes.map((p2) => p2.eventStream?.streamId ?? null).filter((id) => id !== null);
1030
+ const streamsData = await amux.getStreams(streamIds);
1031
+ const streamMap = new Map(streamsData.map((s) => [s.id, s]));
446
1032
  return {
447
1033
  ...window,
448
1034
  panes: windowPanes.map((p2) => ({
449
1035
  ...p2,
450
- session: p2.content?.type === "session" ? sessionMap.get(p2.content.sessionId) ?? null : null
1036
+ stream: p2.eventStream ? streamMap.get(p2.eventStream.streamId) ?? null : null
451
1037
  }))
452
1038
  };
453
1039
  }),
454
1040
  /**
455
1041
  * Create a new window with a single pane.
456
- * If agentConfigId is not provided, pane is created with no content (shows picker).
1042
+ * If streamConfigId is not provided, pane is created with no content (shows picker).
457
1043
  */
458
1044
  create: publicProcedure.input(z.object({
459
1045
  id: z.string().optional(),
460
1046
  title: z.string().optional(),
461
1047
  directory: z.string().optional(),
1048
+ streamConfigId: z.string().optional(),
1049
+ // Legacy alias
462
1050
  agentConfigId: z.string().optional()
463
1051
  })).mutation(async ({ input }) => {
464
1052
  const directory = input.directory ?? process.cwd();
465
- let session = null;
466
- if (input.agentConfigId) {
467
- session = await amux.createSession({
1053
+ const configId = input.streamConfigId ?? input.agentConfigId;
1054
+ let stream = null;
1055
+ if (configId) {
1056
+ stream = await amux.createStream({
468
1057
  directory,
469
- agentConfigId: input.agentConfigId
1058
+ streamConfigId: configId
470
1059
  });
471
1060
  }
472
- const windowId = input.id ?? randomUUID2();
473
- const paneId = randomUUID2();
1061
+ const windowId = input.id ?? randomUUID3();
1062
+ const paneId = randomUUID3();
474
1063
  const existingCount = db.select().from(windows).all().length;
475
1064
  const layout = createLeaf(paneId);
476
1065
  db.insert(windows).values({
@@ -483,16 +1072,17 @@ var windowsRouter = router({
483
1072
  db.insert(panes).values({
484
1073
  id: paneId,
485
1074
  windowId,
486
- content: session ? { type: "session", sessionId: session.id } : null
1075
+ content: null,
1076
+ eventStream: stream ? { type: stream.streamType, streamId: stream.id } : null
487
1077
  }).run();
488
1078
  db.insert(uiState).values({ key: "active_window_id", value: windowId }).onConflictDoUpdate({ target: uiState.key, set: { value: windowId } }).run();
489
- const newWindow = db.select().from(windows).where(eq2(windows.id, windowId)).get();
490
- const newPanes = db.select().from(panes).where(eq2(panes.windowId, windowId)).all();
1079
+ const newWindow = db.select().from(windows).where(eq3(windows.id, windowId)).get();
1080
+ const newPanes = db.select().from(panes).where(eq3(panes.windowId, windowId)).all();
491
1081
  return {
492
1082
  ...newWindow,
493
1083
  panes: newPanes.map((p2) => ({
494
1084
  ...p2,
495
- session: session && p2.content?.type === "session" && p2.content.sessionId === session.id ? session : null
1085
+ stream: stream && p2.eventStream?.streamId === stream.id ? stream : null
496
1086
  }))
497
1087
  };
498
1088
  }),
@@ -509,28 +1099,28 @@ var windowsRouter = router({
509
1099
  updates.hasCustomTitle = true;
510
1100
  }
511
1101
  updates.lastAccessedAt = /* @__PURE__ */ new Date();
512
- db.update(windows).set(updates).where(eq2(windows.id, input.id)).run();
513
- return db.select().from(windows).where(eq2(windows.id, input.id)).get();
1102
+ db.update(windows).set(updates).where(eq3(windows.id, input.id)).run();
1103
+ return db.select().from(windows).where(eq3(windows.id, input.id)).get();
514
1104
  }),
515
1105
  /**
516
- * Delete a window and all its panes/sessions.
1106
+ * Delete a window and all its panes/streams.
517
1107
  */
518
1108
  delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
519
- const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
1109
+ const windowPanes = db.select().from(panes).where(eq3(panes.windowId, input.id)).all();
520
1110
  for (const pane of windowPanes) {
521
- if (pane.content?.type === "session") {
522
- await amux.deleteSession(pane.content.sessionId);
1111
+ if (pane.eventStream) {
1112
+ await amux.deleteStream(pane.eventStream.streamId);
523
1113
  }
524
1114
  }
525
- db.delete(windows).where(eq2(windows.id, input.id)).run();
526
- const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
1115
+ db.delete(windows).where(eq3(windows.id, input.id)).run();
1116
+ const activeRow = db.select().from(uiState).where(eq3(uiState.key, "active_window_id")).get();
527
1117
  if (activeRow?.value === input.id) {
528
1118
  const remaining = db.select().from(windows).orderBy(windows.createdAt).all();
529
1119
  const newActiveId = remaining[remaining.length - 1]?.id ?? null;
530
1120
  if (newActiveId) {
531
- db.update(uiState).set({ value: newActiveId }).where(eq2(uiState.key, "active_window_id")).run();
1121
+ db.update(uiState).set({ value: newActiveId }).where(eq3(uiState.key, "active_window_id")).run();
532
1122
  } else {
533
- db.delete(uiState).where(eq2(uiState.key, "active_window_id")).run();
1123
+ db.delete(uiState).where(eq3(uiState.key, "active_window_id")).run();
534
1124
  }
535
1125
  }
536
1126
  return { ok: true };
@@ -540,15 +1130,15 @@ var windowsRouter = router({
540
1130
  */
541
1131
  setActive: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
542
1132
  db.insert(uiState).values({ key: "active_window_id", value: input.id }).onConflictDoUpdate({ target: uiState.key, set: { value: input.id } }).run();
543
- db.update(windows).set({ lastAccessedAt: /* @__PURE__ */ new Date() }).where(eq2(windows.id, input.id)).run();
1133
+ db.update(windows).set({ lastAccessedAt: /* @__PURE__ */ new Date() }).where(eq3(windows.id, input.id)).run();
544
1134
  return { ok: true };
545
1135
  })
546
1136
  });
547
1137
 
548
1138
  // src/trpc/layout.ts
549
1139
  import { z as z2 } from "zod";
550
- import { randomUUID as randomUUID3 } from "crypto";
551
- import { eq as eq3 } from "drizzle-orm";
1140
+ import { randomUUID as randomUUID4 } from "crypto";
1141
+ import { eq as eq4 } from "drizzle-orm";
552
1142
  var amux2;
553
1143
  function setAmuxBridge2(bridge) {
554
1144
  amux2 = bridge;
@@ -566,20 +1156,20 @@ var layoutRouter = router({
566
1156
  newPaneId: z2.string().optional(),
567
1157
  newSessionId: z2.string().optional()
568
1158
  })).mutation(async ({ input }) => {
569
- const sourcePane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
1159
+ const sourcePane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
570
1160
  if (!sourcePane) {
571
1161
  throw new Error(`Pane ${input.paneId} not found`);
572
1162
  }
573
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
1163
+ const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
574
1164
  if (!window) {
575
1165
  throw new Error(`Window ${input.windowId} not found`);
576
1166
  }
577
- const newContent = null;
578
- const newPaneId = input.newPaneId ?? randomUUID3();
1167
+ const newPaneId = input.newPaneId ?? randomUUID4();
579
1168
  db.insert(panes).values({
580
1169
  id: newPaneId,
581
1170
  windowId: input.windowId,
582
- content: newContent
1171
+ content: null,
1172
+ eventStream: null
583
1173
  }).run();
584
1174
  const currentLayout = window.layout;
585
1175
  const newLayout = splitPane(currentLayout, input.paneId, newPaneId, input.direction);
@@ -587,19 +1177,22 @@ var layoutRouter = router({
587
1177
  layout: newLayout,
588
1178
  activePaneId: newPaneId,
589
1179
  lastAccessedAt: /* @__PURE__ */ new Date()
590
- }).where(eq3(windows.id, input.windowId)).run();
1180
+ }).where(eq4(windows.id, input.windowId)).run();
591
1181
  const now = /* @__PURE__ */ new Date();
592
1182
  return {
593
1183
  newPaneId,
594
- newSessionId: null,
1184
+ newStreamId: null,
595
1185
  layout: newLayout,
596
1186
  newPane: {
597
1187
  id: newPaneId,
598
1188
  windowId: input.windowId,
599
- content: newContent,
1189
+ content: null,
1190
+ eventStream: null,
600
1191
  createdAt: now.toISOString(),
601
- session: null
602
- }
1192
+ stream: null
1193
+ },
1194
+ // Legacy aliases
1195
+ newSessionId: null
603
1196
  };
604
1197
  }),
605
1198
  /**
@@ -609,27 +1202,27 @@ var layoutRouter = router({
609
1202
  windowId: z2.string(),
610
1203
  paneId: z2.string()
611
1204
  })).mutation(async ({ input }) => {
612
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
1205
+ const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
613
1206
  if (!window) {
614
1207
  throw new Error(`Window ${input.windowId} not found`);
615
1208
  }
616
- const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
1209
+ const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
617
1210
  if (!pane) {
618
1211
  throw new Error(`Pane ${input.paneId} not found`);
619
1212
  }
620
1213
  const currentLayout = window.layout;
621
1214
  const allPaneIds = getAllPaneIds(currentLayout);
622
1215
  if (allPaneIds.length === 1) {
623
- if (pane.content?.type === "session") {
624
- await amux2.deleteSession(pane.content.sessionId);
1216
+ if (pane.eventStream) {
1217
+ await amux2.deleteStream(pane.eventStream.streamId);
625
1218
  }
626
- db.delete(windows).where(eq3(windows.id, input.windowId)).run();
1219
+ db.delete(windows).where(eq4(windows.id, input.windowId)).run();
627
1220
  return { windowClosed: true, layout: null, activePaneId: null };
628
1221
  }
629
- if (pane.content?.type === "session") {
630
- await amux2.deleteSession(pane.content.sessionId);
1222
+ if (pane.eventStream) {
1223
+ await amux2.deleteStream(pane.eventStream.streamId);
631
1224
  }
632
- db.delete(panes).where(eq3(panes.id, input.paneId)).run();
1225
+ db.delete(panes).where(eq4(panes.id, input.paneId)).run();
633
1226
  const newLayout = removePane(currentLayout, input.paneId);
634
1227
  if (!newLayout) {
635
1228
  throw new Error("Failed to remove pane from layout");
@@ -639,7 +1232,7 @@ var layoutRouter = router({
639
1232
  layout: newLayout,
640
1233
  activePaneId: newActivePaneId,
641
1234
  lastAccessedAt: /* @__PURE__ */ new Date()
642
- }).where(eq3(windows.id, input.windowId)).run();
1235
+ }).where(eq4(windows.id, input.windowId)).run();
643
1236
  return {
644
1237
  windowClosed: false,
645
1238
  layout: newLayout,
@@ -656,7 +1249,7 @@ var layoutRouter = router({
656
1249
  db.update(windows).set({
657
1250
  activePaneId: input.paneId,
658
1251
  lastAccessedAt: /* @__PURE__ */ new Date()
659
- }).where(eq3(windows.id, input.windowId)).run();
1252
+ }).where(eq4(windows.id, input.windowId)).run();
660
1253
  return { ok: true };
661
1254
  }),
662
1255
  /**
@@ -667,7 +1260,7 @@ var layoutRouter = router({
667
1260
  currentPaneId: z2.string(),
668
1261
  direction: z2.enum(["left", "right", "up", "down"])
669
1262
  })).mutation(async ({ input }) => {
670
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
1263
+ const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
671
1264
  if (!window) {
672
1265
  throw new Error(`Window ${input.windowId} not found`);
673
1266
  }
@@ -677,7 +1270,7 @@ var layoutRouter = router({
677
1270
  db.update(windows).set({
678
1271
  activePaneId: adjacentPaneId,
679
1272
  lastAccessedAt: /* @__PURE__ */ new Date()
680
- }).where(eq3(windows.id, input.windowId)).run();
1273
+ }).where(eq4(windows.id, input.windowId)).run();
681
1274
  return { newActivePaneId: adjacentPaneId };
682
1275
  }
683
1276
  return { newActivePaneId: null };
@@ -690,93 +1283,109 @@ var layoutRouter = router({
690
1283
  path: z2.array(z2.enum(["first", "second"])),
691
1284
  ratio: z2.number().min(10).max(90)
692
1285
  })).mutation(async ({ input }) => {
693
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
1286
+ const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
694
1287
  if (!window) {
695
1288
  throw new Error(`Window ${input.windowId} not found`);
696
1289
  }
697
1290
  const currentLayout = window.layout;
698
1291
  const newLayout = updateRatio(currentLayout, input.path, input.ratio);
699
- db.update(windows).set({ layout: newLayout }).where(eq3(windows.id, input.windowId)).run();
1292
+ db.update(windows).set({ layout: newLayout }).where(eq4(windows.id, input.windowId)).run();
700
1293
  return { layout: newLayout };
701
1294
  }),
702
1295
  /**
703
- * Set the session for an empty pane (used when user picks agent/shell type).
704
- * Creates a session in amux and updates the pane.
705
- */
706
- setSession: publicProcedure.input(z2.object({
707
- paneId: z2.string(),
708
- agentConfigId: z2.string(),
709
- directory: z2.string().optional()
710
- })).mutation(async ({ input }) => {
711
- const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
712
- if (!pane) {
713
- throw new Error(`Pane ${input.paneId} not found`);
714
- }
715
- if (pane.content) {
716
- throw new Error(`Pane ${input.paneId} already has content`);
717
- }
718
- const session = await amux2.createSession({
719
- directory: input.directory ?? process.cwd(),
720
- agentConfigId: input.agentConfigId
721
- });
722
- db.update(panes).set({ content: { type: "session", sessionId: session.id } }).where(eq3(panes.id, input.paneId)).run();
723
- await amux2.startAgent(session.id);
724
- return {
725
- sessionId: session.id,
726
- content: { type: "session", sessionId: session.id },
727
- session: {
728
- id: session.id,
729
- directory: session.directory,
730
- agentConfigId: session.agentConfigId,
731
- model: session.model,
732
- mode: session.mode,
733
- createdAt: session.createdAt.toISOString(),
734
- acpSessionId: session.acpSessionId
735
- }
736
- };
737
- }),
738
- /**
739
- * Set browser content for an empty pane.
1296
+ * Set static content for a pane (browser URL, etc.).
1297
+ * Upsert behavior - works for both initial set and updates.
740
1298
  */
741
- setBrowserContent: publicProcedure.input(z2.object({
1299
+ setPaneContent: publicProcedure.input(z2.object({
742
1300
  paneId: z2.string(),
743
- url: z2.string().url()
1301
+ content: z2.record(z2.unknown()).nullable(),
1302
+ rendererId: z2.string().optional()
744
1303
  })).mutation(async ({ input }) => {
745
- const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
1304
+ const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
746
1305
  if (!pane) {
747
1306
  throw new Error(`Pane ${input.paneId} not found`);
748
1307
  }
749
- if (pane.content) {
750
- throw new Error(`Pane ${input.paneId} already has content`);
751
- }
752
- const content = { type: "browser", url: input.url };
753
- db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
754
- return { content };
1308
+ db.update(panes).set({
1309
+ content: input.content,
1310
+ rendererId: input.rendererId ?? null
1311
+ }).where(eq4(panes.id, input.paneId)).run();
1312
+ return { content: input.content, rendererId: input.rendererId };
755
1313
  }),
756
1314
  /**
757
- * Update the URL of an existing browser pane.
1315
+ * Set an event stream for a pane (agent or shell).
1316
+ * Creates stream in amux and starts the backend.
758
1317
  */
759
- updateBrowserUrl: publicProcedure.input(z2.object({
1318
+ setPaneEventStream: publicProcedure.input(z2.object({
760
1319
  paneId: z2.string(),
761
- url: z2.string().url()
1320
+ streamConfigId: z2.string(),
1321
+ directory: z2.string().optional(),
1322
+ rendererId: z2.string().optional(),
1323
+ // Legacy alias
1324
+ agentConfigId: z2.string().optional()
762
1325
  })).mutation(async ({ input }) => {
763
- const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
1326
+ const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
764
1327
  if (!pane) {
765
1328
  throw new Error(`Pane ${input.paneId} not found`);
766
1329
  }
767
- if (pane.content?.type !== "browser") {
768
- throw new Error(`Pane ${input.paneId} is not a browser pane`);
1330
+ if (pane.eventStream) {
1331
+ throw new Error(`Pane ${input.paneId} already has an event stream`);
769
1332
  }
770
- const content = { type: "browser", url: input.url };
771
- db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
772
- return { content };
1333
+ const configId = input.streamConfigId ?? input.agentConfigId;
1334
+ const stream = await amux2.createStream({
1335
+ directory: input.directory ?? process.cwd(),
1336
+ streamConfigId: configId
1337
+ });
1338
+ const eventStream = { type: stream.streamType, streamId: stream.id };
1339
+ db.update(panes).set({
1340
+ eventStream,
1341
+ rendererId: input.rendererId ?? null
1342
+ }).where(eq4(panes.id, input.paneId)).run();
1343
+ await amux2.startStream(stream.id);
1344
+ return {
1345
+ streamId: stream.id,
1346
+ eventStream,
1347
+ stream: {
1348
+ id: stream.id,
1349
+ directory: stream.directory,
1350
+ streamConfigId: stream.streamConfigId,
1351
+ streamType: stream.streamType,
1352
+ model: stream.model,
1353
+ mode: stream.mode,
1354
+ createdAt: stream.createdAt.toISOString()
1355
+ },
1356
+ // Legacy aliases
1357
+ sessionId: stream.id,
1358
+ process: eventStream,
1359
+ session: {
1360
+ id: stream.id,
1361
+ directory: stream.directory,
1362
+ agentConfigId: stream.streamConfigId,
1363
+ model: stream.model,
1364
+ mode: stream.mode,
1365
+ createdAt: stream.createdAt.toISOString()
1366
+ }
1367
+ };
773
1368
  })
774
1369
  });
775
1370
 
776
1371
  // src/trpc/agents.ts
777
1372
  import { z as z3 } from "zod";
778
- import { eq as eq4 } from "drizzle-orm";
779
1373
  import { observable } from "@trpc/server/observable";
1374
+
1375
+ // src/lib/panes.ts
1376
+ import { eq as eq5 } from "drizzle-orm";
1377
+ function getStreamIdForPane(paneId) {
1378
+ const pane = db.select().from(panes).where(eq5(panes.id, paneId)).get();
1379
+ if (!pane) {
1380
+ throw new Error(`Pane ${paneId} not found`);
1381
+ }
1382
+ if (!pane.eventStream) {
1383
+ throw new Error(`Pane ${paneId} does not have an event stream`);
1384
+ }
1385
+ return pane.eventStream.streamId;
1386
+ }
1387
+
1388
+ // src/trpc/agents.ts
780
1389
  var amux3;
781
1390
  function setAmuxBridge3(bridge) {
782
1391
  amux3 = bridge;
@@ -784,71 +1393,61 @@ function setAmuxBridge3(bridge) {
784
1393
  var paneInput = z3.object({
785
1394
  paneId: z3.string()
786
1395
  });
787
- function getSessionIdForPane(paneId) {
788
- const pane = db.select().from(panes).where(eq4(panes.id, paneId)).get();
789
- if (!pane) {
790
- throw new Error(`Pane ${paneId} not found`);
791
- }
792
- if (pane.content?.type !== "session") {
793
- throw new Error(`Pane ${paneId} is not a session pane`);
794
- }
795
- return pane.content.sessionId;
796
- }
797
- var agentsRouter = router({
1396
+ var streamsRouter = router({
798
1397
  /**
799
- * Start agent for a pane.
1398
+ * Start stream for a pane.
800
1399
  */
801
1400
  start: publicProcedure.input(paneInput).mutation(async ({ input }) => {
802
- const sessionId = getSessionIdForPane(input.paneId);
803
- return amux3.startAgent(sessionId);
1401
+ const streamId = getStreamIdForPane(input.paneId);
1402
+ return amux3.startStream(streamId);
804
1403
  }),
805
1404
  /**
806
- * Stop agent for a pane.
1405
+ * Stop stream for a pane.
807
1406
  */
808
1407
  stop: publicProcedure.input(paneInput).mutation(async ({ input }) => {
809
- const sessionId = getSessionIdForPane(input.paneId);
810
- await amux3.stopAgent(sessionId);
1408
+ const streamId = getStreamIdForPane(input.paneId);
1409
+ await amux3.stopStream(streamId);
811
1410
  return { ok: true };
812
1411
  }),
813
1412
  /**
814
- * Send a prompt to a pane's agent.
1413
+ * Send input to a pane's stream.
815
1414
  */
816
- prompt: publicProcedure.input(z3.object({
1415
+ input: publicProcedure.input(z3.object({
817
1416
  paneId: z3.string(),
818
1417
  message: z3.string()
819
1418
  })).mutation(async ({ input }) => {
820
- const sessionId = getSessionIdForPane(input.paneId);
821
- await amux3.prompt(sessionId, input.message);
1419
+ const streamId = getStreamIdForPane(input.paneId);
1420
+ await amux3.input(streamId, input.message);
822
1421
  return { ok: true };
823
1422
  }),
824
1423
  /**
825
1424
  * Cancel the current prompt.
826
1425
  */
827
1426
  cancel: publicProcedure.input(paneInput).mutation(async ({ input }) => {
828
- const sessionId = getSessionIdForPane(input.paneId);
829
- await amux3.cancel(sessionId);
1427
+ const streamId = getStreamIdForPane(input.paneId);
1428
+ await amux3.cancel(streamId);
830
1429
  return { ok: true };
831
1430
  }),
832
1431
  /**
833
- * Set agent mode.
1432
+ * Set stream mode.
834
1433
  */
835
1434
  setMode: publicProcedure.input(z3.object({
836
1435
  paneId: z3.string(),
837
1436
  modeId: z3.string()
838
1437
  })).mutation(async ({ input }) => {
839
- const sessionId = getSessionIdForPane(input.paneId);
840
- await amux3.setMode(sessionId, input.modeId);
1438
+ const streamId = getStreamIdForPane(input.paneId);
1439
+ await amux3.setMode(streamId, input.modeId);
841
1440
  return { ok: true };
842
1441
  }),
843
1442
  /**
844
- * Set agent model.
1443
+ * Set stream model.
845
1444
  */
846
1445
  setModel: publicProcedure.input(z3.object({
847
1446
  paneId: z3.string(),
848
1447
  modelId: z3.string()
849
1448
  })).mutation(async ({ input }) => {
850
- const sessionId = getSessionIdForPane(input.paneId);
851
- await amux3.setModel(sessionId, input.modelId);
1449
+ const streamId = getStreamIdForPane(input.paneId);
1450
+ await amux3.setModel(streamId, input.modelId);
852
1451
  return { ok: true };
853
1452
  }),
854
1453
  /**
@@ -859,17 +1458,17 @@ var agentsRouter = router({
859
1458
  requestId: z3.string(),
860
1459
  optionId: z3.string()
861
1460
  })).mutation(async ({ input }) => {
862
- const sessionId = getSessionIdForPane(input.paneId);
863
- amux3.respondPermission(sessionId, input.requestId, input.optionId);
1461
+ const streamId = getStreamIdForPane(input.paneId);
1462
+ amux3.respondPermission(streamId, input.requestId, input.optionId);
864
1463
  return { ok: true };
865
1464
  }),
866
1465
  /**
867
- * Subscribe to updates for a pane's agent session.
1466
+ * Subscribe to updates for a pane's stream.
868
1467
  */
869
1468
  subscribe: publicProcedure.input(paneInput).subscription(({ input }) => {
870
- const sessionId = getSessionIdForPane(input.paneId);
1469
+ const streamId = getStreamIdForPane(input.paneId);
871
1470
  return observable((emit) => {
872
- return amux3.subscribeToSession(sessionId, (update) => {
1471
+ return amux3.subscribeToStream(streamId, (update) => {
873
1472
  emit.next(update);
874
1473
  });
875
1474
  });
@@ -878,21 +1477,10 @@ var agentsRouter = router({
878
1477
 
879
1478
  // src/trpc/files.ts
880
1479
  import { z as z4 } from "zod";
881
- import { eq as eq5 } from "drizzle-orm";
882
1480
  var amux4;
883
1481
  function setAmuxBridge4(bridge) {
884
1482
  amux4 = bridge;
885
1483
  }
886
- function getSessionIdForPane2(paneId) {
887
- const pane = db.select().from(panes).where(eq5(panes.id, paneId)).get();
888
- if (!pane) {
889
- throw new Error(`Pane ${paneId} not found`);
890
- }
891
- if (pane.content?.type !== "session") {
892
- throw new Error(`Pane ${paneId} is not a session pane`);
893
- }
894
- return pane.content.sessionId;
895
- }
896
1484
  var filesRouter = router({
897
1485
  /**
898
1486
  * List files/directories matching a partial path for autocomplete.
@@ -903,8 +1491,8 @@ var filesRouter = router({
903
1491
  partialPath: z4.string(),
904
1492
  limit: z4.number().default(20)
905
1493
  })).query(async ({ input }) => {
906
- const sessionId = getSessionIdForPane2(input.paneId);
907
- return amux4.listFilesForAutocomplete(sessionId, input.partialPath, input.limit);
1494
+ const streamId = getStreamIdForPane(input.paneId);
1495
+ return amux4.listFilesForAutocomplete(streamId, input.partialPath, input.limit);
908
1496
  })
909
1497
  });
910
1498
 
@@ -918,15 +1506,15 @@ function setAmuxBridge5(bridge) {
918
1506
  var paneInput2 = z5.object({
919
1507
  paneId: z5.string()
920
1508
  });
921
- function getSessionIdForPane3(paneId) {
1509
+ function getStreamIdForPane2(paneId) {
922
1510
  const pane = db.select().from(panes).where(eq6(panes.id, paneId)).get();
923
1511
  if (!pane) {
924
1512
  throw new Error(`Pane ${paneId} not found`);
925
1513
  }
926
- if (pane.content?.type !== "session") {
927
- throw new Error(`Pane ${paneId} is not a session pane`);
1514
+ if (!pane.eventStream) {
1515
+ throw new Error(`Pane ${paneId} does not have an event stream`);
928
1516
  }
929
- return pane.content.sessionId;
1517
+ return pane.eventStream.streamId;
930
1518
  }
931
1519
  var terminalsRouter = router({
932
1520
  /**
@@ -938,8 +1526,8 @@ var terminalsRouter = router({
938
1526
  data: z5.string()
939
1527
  })
940
1528
  ).mutation(({ input }) => {
941
- const sessionId = getSessionIdForPane3(input.paneId);
942
- amux5.terminalWrite(sessionId, input.data);
1529
+ const streamId = getStreamIdForPane2(input.paneId);
1530
+ amux5.terminalWrite(streamId, input.data);
943
1531
  return { ok: true };
944
1532
  }),
945
1533
  /**
@@ -952,16 +1540,16 @@ var terminalsRouter = router({
952
1540
  rows: z5.number().int().positive()
953
1541
  })
954
1542
  ).mutation(({ input }) => {
955
- const sessionId = getSessionIdForPane3(input.paneId);
956
- amux5.terminalResize(sessionId, input.cols, input.rows);
1543
+ const streamId = getStreamIdForPane2(input.paneId);
1544
+ amux5.terminalResize(streamId, input.cols, input.rows);
957
1545
  return { ok: true };
958
1546
  }),
959
1547
  /**
960
- * Check if a pane is a terminal session.
1548
+ * Check if a pane is a terminal stream.
961
1549
  */
962
1550
  isTerminal: publicProcedure.input(paneInput2).query(({ input }) => {
963
- const sessionId = getSessionIdForPane3(input.paneId);
964
- return { isTerminal: amux5.isTerminalSession(sessionId) };
1551
+ const streamId = getStreamIdForPane2(input.paneId);
1552
+ return { isTerminal: amux5.isTerminalStream(streamId) };
965
1553
  }),
966
1554
  /**
967
1555
  * Set terminal title (from OSC escape sequence).
@@ -970,12 +1558,264 @@ var terminalsRouter = router({
970
1558
  paneId: z5.string(),
971
1559
  title: z5.string()
972
1560
  })).mutation(({ input }) => {
973
- const sessionId = getSessionIdForPane3(input.paneId);
974
- amux5.updateSessionTitle(sessionId, input.title);
1561
+ const streamId = getStreamIdForPane2(input.paneId);
1562
+ amux5.updateStreamTitle(streamId, input.title);
975
1563
  return { ok: true };
976
1564
  })
977
1565
  });
978
1566
 
1567
+ // src/trpc/renderers.ts
1568
+ import { observable as observable2 } from "@trpc/server/observable";
1569
+
1570
+ // src/lib/rendererWatcher.ts
1571
+ import chokidar2 from "chokidar";
1572
+ import * as path7 from "path";
1573
+ import * as os3 from "os";
1574
+ import * as fs7 from "fs/promises";
1575
+ import { EventEmitter as EventEmitter2 } from "events";
1576
+
1577
+ // src/lib/rendererCompiler.ts
1578
+ import { build as build2 } from "esbuild";
1579
+ import * as fs6 from "fs/promises";
1580
+ import * as path6 from "path";
1581
+ async function compileRenderer(pluginDir) {
1582
+ const metaPath = path6.join(pluginDir, "meta.json");
1583
+ const entryPath = path6.join(pluginDir, "index.tsx");
1584
+ const pkgPath = path6.join(pluginDir, "package.json");
1585
+ let meta;
1586
+ try {
1587
+ const metaContent = await fs6.readFile(metaPath, "utf-8");
1588
+ meta = JSON.parse(metaContent);
1589
+ } catch (err) {
1590
+ throw new Error(
1591
+ `Failed to read meta file for ${pluginDir}. Expected ${metaPath} with { name, target } properties. Error: ${err instanceof Error ? err.message : String(err)}`
1592
+ );
1593
+ }
1594
+ if (!meta.name || typeof meta.name !== "string") {
1595
+ throw new Error(`Meta file ${metaPath} must have a 'name' string property`);
1596
+ }
1597
+ if (!meta.target || !meta.target.kind) {
1598
+ throw new Error(`Meta file ${metaPath} must have a 'target' object with 'kind' property`);
1599
+ }
1600
+ let hasPackageJson = false;
1601
+ try {
1602
+ const pkgContent = await fs6.readFile(pkgPath, "utf-8");
1603
+ const pkg = JSON.parse(pkgContent);
1604
+ hasPackageJson = true;
1605
+ const deps = pkg.dependencies || {};
1606
+ if (deps["react"] || deps["react-dom"]) {
1607
+ throw new Error(
1608
+ `Plugin "${meta.name}": react and react-dom must be in peerDependencies, not dependencies. This ensures plugins share the same React instance as the host app.`
1609
+ );
1610
+ }
1611
+ } catch (err) {
1612
+ if (err instanceof Error && err.message.includes("peerDependencies")) {
1613
+ throw err;
1614
+ }
1615
+ }
1616
+ const result = await build2({
1617
+ entryPoints: [entryPath],
1618
+ bundle: true,
1619
+ write: false,
1620
+ format: "esm",
1621
+ jsx: "automatic",
1622
+ target: "es2020",
1623
+ external: ["react", "react-dom", "react/jsx-runtime", "@bytespell/shella-sdk", "@bytespell/shella-sdk/components/ui"],
1624
+ // If plugin has package.json, look for node_modules
1625
+ nodePaths: hasPackageJson ? [path6.join(pluginDir, "node_modules")] : [],
1626
+ minify: false
1627
+ });
1628
+ const code = result.outputFiles[0].text;
1629
+ const id = `user:${path6.basename(pluginDir)}`;
1630
+ return {
1631
+ id,
1632
+ name: meta.name,
1633
+ description: meta.description,
1634
+ target: meta.target,
1635
+ code
1636
+ };
1637
+ }
1638
+ async function isValidPlugin(dirPath) {
1639
+ try {
1640
+ await fs6.access(path6.join(dirPath, "meta.json"));
1641
+ await fs6.access(path6.join(dirPath, "index.tsx"));
1642
+ return true;
1643
+ } catch {
1644
+ return false;
1645
+ }
1646
+ }
1647
+
1648
+ // src/lib/rendererWatcher.ts
1649
+ var RendererEventEmitter = class extends EventEmitter2 {
1650
+ emit(event, data) {
1651
+ return super.emit(event, data);
1652
+ }
1653
+ on(event, listener) {
1654
+ return super.on(event, listener);
1655
+ }
1656
+ off(event, listener) {
1657
+ return super.off(event, listener);
1658
+ }
1659
+ };
1660
+ function watchRenderers(emitter) {
1661
+ const userPath = path7.join(os3.homedir(), ".shella", "renderers");
1662
+ ensureDir2(userPath);
1663
+ const knownPlugins = /* @__PURE__ */ new Map();
1664
+ const pendingCompiles = /* @__PURE__ */ new Map();
1665
+ const watcher = chokidar2.watch(userPath, {
1666
+ ignoreInitial: false,
1667
+ persistent: true,
1668
+ awaitWriteFinish: {
1669
+ stabilityThreshold: 100,
1670
+ pollInterval: 50
1671
+ },
1672
+ depth: 3
1673
+ // Watch inside plugin folders for nested components
1674
+ });
1675
+ const getPluginDir = (filePath) => {
1676
+ const relative3 = path7.relative(userPath, filePath);
1677
+ const parts = relative3.split(path7.sep);
1678
+ if (parts.length >= 1 && parts[0]) {
1679
+ return path7.join(userPath, parts[0]);
1680
+ }
1681
+ return null;
1682
+ };
1683
+ const scheduleCompile = (pluginDir) => {
1684
+ if (pendingCompiles.has(pluginDir)) {
1685
+ clearTimeout(pendingCompiles.get(pluginDir));
1686
+ }
1687
+ pendingCompiles.set(
1688
+ pluginDir,
1689
+ setTimeout(async () => {
1690
+ pendingCompiles.delete(pluginDir);
1691
+ await handlePluginChange(pluginDir);
1692
+ }, 100)
1693
+ );
1694
+ };
1695
+ const handlePluginChange = async (pluginDir) => {
1696
+ if (!await isValidPlugin(pluginDir)) return;
1697
+ try {
1698
+ const compiled = await compileRenderer(pluginDir);
1699
+ if (!knownPlugins.has(pluginDir)) {
1700
+ knownPlugins.set(pluginDir, true);
1701
+ console.log(`[renderers] Added: ${compiled.id}`);
1702
+ emitter.emit("renderer:added", compiled);
1703
+ } else {
1704
+ console.log(`[renderers] Updated: ${compiled.id}`);
1705
+ emitter.emit("renderer:updated", { id: compiled.id, code: compiled.code });
1706
+ }
1707
+ } catch (err) {
1708
+ console.error(`[renderers] Error compiling ${pluginDir}:`, err);
1709
+ emitter.emit("renderer:error", {
1710
+ filePath: pluginDir,
1711
+ error: err instanceof Error ? err : new Error(String(err))
1712
+ });
1713
+ }
1714
+ };
1715
+ watcher.on("add", (filePath) => {
1716
+ const pluginDir = getPluginDir(filePath);
1717
+ if (pluginDir) scheduleCompile(pluginDir);
1718
+ });
1719
+ watcher.on("change", (filePath) => {
1720
+ const pluginDir = getPluginDir(filePath);
1721
+ if (pluginDir) scheduleCompile(pluginDir);
1722
+ });
1723
+ watcher.on("unlinkDir", (dirPath) => {
1724
+ if (knownPlugins.has(dirPath)) {
1725
+ const id = `user:${path7.basename(dirPath)}`;
1726
+ knownPlugins.delete(dirPath);
1727
+ console.log(`[renderers] Removed: ${id}`);
1728
+ emitter.emit("renderer:removed", { id });
1729
+ }
1730
+ });
1731
+ watcher.on("error", (err) => {
1732
+ console.error("[renderers] Watcher error:", err);
1733
+ });
1734
+ return () => {
1735
+ for (const timeout of pendingCompiles.values()) {
1736
+ clearTimeout(timeout);
1737
+ }
1738
+ pendingCompiles.clear();
1739
+ watcher.close();
1740
+ };
1741
+ }
1742
+ async function ensureDir2(dirPath) {
1743
+ try {
1744
+ await fs7.mkdir(dirPath, { recursive: true });
1745
+ } catch {
1746
+ }
1747
+ }
1748
+ async function scanRenderers() {
1749
+ const userPath = path7.join(os3.homedir(), ".shella", "renderers");
1750
+ const renderers = [];
1751
+ try {
1752
+ const entries = await fs7.readdir(userPath, { withFileTypes: true });
1753
+ for (const entry of entries) {
1754
+ if (!entry.isDirectory()) continue;
1755
+ const pluginDir = path7.join(userPath, entry.name);
1756
+ if (await isValidPlugin(pluginDir)) {
1757
+ try {
1758
+ const compiled = await compileRenderer(pluginDir);
1759
+ renderers.push(compiled);
1760
+ } catch (err) {
1761
+ console.error(`[renderers] Error compiling ${pluginDir}:`, err);
1762
+ }
1763
+ }
1764
+ }
1765
+ } catch {
1766
+ }
1767
+ return renderers;
1768
+ }
1769
+
1770
+ // src/trpc/renderers.ts
1771
+ var rendererEmitter = new RendererEventEmitter();
1772
+ var stopWatcher = null;
1773
+ function initRendererWatcher() {
1774
+ if (stopWatcher) {
1775
+ stopWatcher();
1776
+ }
1777
+ stopWatcher = watchRenderers(rendererEmitter);
1778
+ }
1779
+ var renderersRouter = router({
1780
+ /**
1781
+ * List all currently available user renderers
1782
+ * Used for initial hydration when client connects.
1783
+ */
1784
+ list: publicProcedure.query(async () => {
1785
+ return scanRenderers();
1786
+ }),
1787
+ /**
1788
+ * Subscribe to renderer additions
1789
+ */
1790
+ onAdded: publicProcedure.subscription(() => {
1791
+ return observable2((emit) => {
1792
+ const handler = (data) => emit.next(data);
1793
+ rendererEmitter.on("renderer:added", handler);
1794
+ return () => rendererEmitter.off("renderer:added", handler);
1795
+ });
1796
+ }),
1797
+ /**
1798
+ * Subscribe to renderer updates (code changes)
1799
+ */
1800
+ onUpdated: publicProcedure.subscription(() => {
1801
+ return observable2((emit) => {
1802
+ const handler = (data) => emit.next(data);
1803
+ rendererEmitter.on("renderer:updated", handler);
1804
+ return () => rendererEmitter.off("renderer:updated", handler);
1805
+ });
1806
+ }),
1807
+ /**
1808
+ * Subscribe to renderer removals
1809
+ */
1810
+ onRemoved: publicProcedure.subscription(() => {
1811
+ return observable2((emit) => {
1812
+ const handler = (data) => emit.next(data);
1813
+ rendererEmitter.on("renderer:removed", handler);
1814
+ return () => rendererEmitter.off("renderer:removed", handler);
1815
+ });
1816
+ })
1817
+ });
1818
+
979
1819
  // src/trpc/router.ts
980
1820
  function initializeRouters(amux6) {
981
1821
  setAmuxBridge(amux6);
@@ -983,39 +1823,60 @@ function initializeRouters(amux6) {
983
1823
  setAmuxBridge3(amux6);
984
1824
  setAmuxBridge4(amux6);
985
1825
  setAmuxBridge5(amux6);
1826
+ initRendererWatcher();
986
1827
  }
987
1828
  var shellaRouter = router({
988
1829
  windows: windowsRouter,
989
1830
  layout: layoutRouter,
990
- agents: agentsRouter,
1831
+ streams: streamsRouter,
991
1832
  files: filesRouter,
992
- terminals: terminalsRouter
1833
+ terminals: terminalsRouter,
1834
+ renderers: renderersRouter,
1835
+ // Legacy alias
1836
+ agents: streamsRouter
993
1837
  });
994
1838
 
995
1839
  // src/index.ts
996
- var __dirname = path2.dirname(fileURLToPath(import.meta.url));
1840
+ discoverAndRegisterConfigs(builtInDiscoverers);
1841
+ var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
997
1842
  function createShellaServer(options = {}) {
998
1843
  const port = options.port ?? 3067;
999
1844
  if (options.verbose) {
1000
1845
  setVerbose(true);
1001
1846
  }
1847
+ const driverEmitter = new DriverEventEmitter();
1848
+ let stopDriverWatcher = null;
1849
+ driverEmitter.on("driver:added", ({ id, name }) => {
1850
+ debug("shella", `Driver registered: ${id} (${name})`);
1851
+ });
1852
+ driverEmitter.on("driver:updated", ({ id, name }) => {
1853
+ debug("shella", `Driver updated: ${id} (${name})`);
1854
+ });
1855
+ driverEmitter.on("driver:removed", ({ id }) => {
1856
+ debug("shella", `Driver removed: ${id}`);
1857
+ });
1858
+ driverEmitter.on("driver:error", ({ pluginDir, error }) => {
1859
+ console.error(`Driver error in ${pluginDir}:`, error.message);
1860
+ });
1002
1861
  const amuxBridge = createAmuxBridge();
1003
1862
  initializeRouters(amuxBridge);
1004
1863
  const app = express();
1005
1864
  app.use(express.json());
1006
- const publicPath = path2.join(__dirname, "..", "public");
1865
+ const publicPath = path8.join(__dirname2, "..", "public");
1007
1866
  app.use(express.static(publicPath));
1008
1867
  app.use("/trpc", createExpressMiddleware({ router: shellaRouter }));
1009
1868
  app.get("/health", (_req, res) => res.json({ status: "ok" }));
1010
1869
  app.get("/{*splat}", (_req, res) => {
1011
- res.sendFile(path2.join(publicPath, "index.html"));
1870
+ res.sendFile(path8.join(publicPath, "index.html"));
1012
1871
  });
1013
1872
  const server = createServer(app);
1014
1873
  const wss = new WebSocketServer({ server, path: "/trpc" });
1015
1874
  applyWSSHandler({ wss, router: shellaRouter });
1016
1875
  return {
1017
- start: () => {
1018
- return new Promise((resolve, reject) => {
1876
+ start: async () => {
1877
+ await scanDrivers();
1878
+ stopDriverWatcher = watchDrivers(driverEmitter);
1879
+ return new Promise((resolve3, reject) => {
1019
1880
  server.once("error", (err) => {
1020
1881
  if (err.code === "EADDRINUSE") {
1021
1882
  reject(new Error(`Port ${port} is already in use. Kill the other process or use a different port.`));
@@ -1027,14 +1888,15 @@ function createShellaServer(options = {}) {
1027
1888
  const url = `http://localhost:${port}`;
1028
1889
  debug("shella", `Running on ${url}`);
1029
1890
  options.onReady?.(url);
1030
- resolve();
1891
+ resolve3();
1031
1892
  });
1032
1893
  });
1033
1894
  },
1034
1895
  stop: async () => {
1035
1896
  debug("shella", "Shutting down...");
1897
+ stopDriverWatcher?.();
1036
1898
  wss.close();
1037
- await agentManager2.stopAll();
1899
+ await muxManager3.stopAll();
1038
1900
  server.close();
1039
1901
  }
1040
1902
  };