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