@bytespell/shella 0.1.11 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +1209 -304
- package/dist/bin/cli.js.map +1 -1
- package/dist/index.js +1209 -304
- package/dist/index.js.map +1 -1
- package/dist/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-DFrDAdNH.js} +1 -1
- package/{public/assets/arc-CCivY36l.js → dist/public/assets/arc-Dn-EBh2M.js} +1 -1
- package/{public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → dist/public/assets/architectureDiagram-VXUJARFQ-cLPhT_VH.js} +1 -1
- package/{public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → dist/public/assets/blockDiagram-VD42YOAC-D9YWCJd4.js} +1 -1
- package/dist/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-7iBiFNxv.js} +1 -1
- package/dist/public/assets/channel-Bc28uGr5.js +1 -0
- package/dist/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DHospLZi.js} +1 -1
- package/dist/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-DYi5BOlX.js} +1 -1
- package/{public/assets/chunk-B4BG7PRW-DU8Wql9S.js → dist/public/assets/chunk-B4BG7PRW-B9aL4JLb.js} +1 -1
- package/{public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → dist/public/assets/chunk-DI55MBZ5-ljO7G9dq.js} +1 -1
- package/dist/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-Cwe-EPLG.js} +1 -1
- package/dist/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DEnaVKU1.js} +1 -1
- package/dist/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-CWDdzKRs.js} +1 -1
- package/dist/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-Bpz-P3mQ.js} +1 -1
- package/dist/public/assets/classDiagram-2ON5EDUG-DJRc91IB.js +1 -0
- package/dist/public/assets/classDiagram-v2-WZHVMYZB-DJRc91IB.js +1 -0
- package/dist/public/assets/clone-CkWk6F_f.js +1 -0
- package/dist/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-Cd4wcD52.js} +1 -1
- package/dist/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-BWl9wqzp.js} +1 -1
- package/dist/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-DGDxxVd8.js} +1 -1
- package/dist/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-iQjjIACd.js} +1 -1
- package/dist/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-D-rDIvx4.js} +1 -1
- package/dist/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-BV8jEZTk.js} +1 -1
- package/{public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → dist/public/assets/erDiagram-Q2GNP2WA-XJ0647Zi.js} +1 -1
- package/{public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → dist/public/assets/flowDiagram-NV44I4VS-DSH4uel9.js} +1 -1
- package/dist/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-Cz4yT-vm.js} +1 -1
- package/dist/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-Y2DLg6xz.js} +1 -1
- package/dist/public/assets/{graph-CtTAhETf.js → graph-z0wljCJ_.js} +1 -1
- package/dist/public/assets/index-CCsulXgB.css +1 -0
- package/dist/public/assets/index-DplAQDze.js +1781 -0
- package/dist/public/assets/index-iDg75qrw.js +23 -0
- package/dist/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-G5NGCtYh.js} +1 -1
- package/dist/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-BQ7qAaof.js} +1 -1
- package/{public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → dist/public/assets/kanban-definition-3W4ZIXB7-DnMJJT5X.js} +1 -1
- package/dist/public/assets/{layout-DolajoeL.js → layout-CbJei3Fo.js} +1 -1
- package/dist/public/assets/{linear-B5Hf7uIN.js → linear-3YD7PkfQ.js} +1 -1
- package/dist/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-DjfX8Ewh.js} +5 -5
- package/dist/public/assets/{min-UFvm8GLY.js → min-Chhh-zX-.js} +1 -1
- package/dist/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-CLM5K3ub.js} +1 -1
- package/dist/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BFiT_I_m.js} +1 -1
- package/{public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → dist/public/assets/quadrantDiagram-AYHSOK5B-Be7QQurL.js} +1 -1
- package/dist/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-5NrHk2Xy.js} +1 -1
- package/{public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → dist/public/assets/sankeyDiagram-TZEHDZUN-DQ8Siqqh.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-Bj_sOA9D.js} +1 -1
- package/{public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → dist/public/assets/stateDiagram-FKZM4ZOC-DncnQ-99.js} +1 -1
- package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BdQ5RGa6.js +1 -0
- package/{public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → dist/public/assets/timeline-definition-IT6M3QCI-C9uFv_GY.js} +1 -1
- package/dist/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-CTzKLdTW.js} +1 -1
- package/{public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → dist/public/assets/xychartDiagram-PRI3JC2R-BP2VkXwS.js} +1 -1
- package/dist/public/index.html +15 -2
- package/package.json +6 -2
- package/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-DFrDAdNH.js} +1 -1
- package/{dist/public/assets/arc-CCivY36l.js → public/assets/arc-Dn-EBh2M.js} +1 -1
- package/{dist/public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → public/assets/architectureDiagram-VXUJARFQ-cLPhT_VH.js} +1 -1
- package/{dist/public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → public/assets/blockDiagram-VD42YOAC-D9YWCJd4.js} +1 -1
- package/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-7iBiFNxv.js} +1 -1
- package/public/assets/channel-Bc28uGr5.js +1 -0
- package/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DHospLZi.js} +1 -1
- package/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-DYi5BOlX.js} +1 -1
- package/{dist/public/assets/chunk-B4BG7PRW-DU8Wql9S.js → public/assets/chunk-B4BG7PRW-B9aL4JLb.js} +1 -1
- package/{dist/public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → public/assets/chunk-DI55MBZ5-ljO7G9dq.js} +1 -1
- package/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-Cwe-EPLG.js} +1 -1
- package/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DEnaVKU1.js} +1 -1
- package/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-CWDdzKRs.js} +1 -1
- package/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-Bpz-P3mQ.js} +1 -1
- package/public/assets/classDiagram-2ON5EDUG-DJRc91IB.js +1 -0
- package/public/assets/classDiagram-v2-WZHVMYZB-DJRc91IB.js +1 -0
- package/public/assets/clone-CkWk6F_f.js +1 -0
- package/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-Cd4wcD52.js} +1 -1
- package/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-BWl9wqzp.js} +1 -1
- package/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-DGDxxVd8.js} +1 -1
- package/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-iQjjIACd.js} +1 -1
- package/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-D-rDIvx4.js} +1 -1
- package/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-BV8jEZTk.js} +1 -1
- package/{dist/public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → public/assets/erDiagram-Q2GNP2WA-XJ0647Zi.js} +1 -1
- package/{dist/public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → public/assets/flowDiagram-NV44I4VS-DSH4uel9.js} +1 -1
- package/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-Cz4yT-vm.js} +1 -1
- package/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-Y2DLg6xz.js} +1 -1
- package/public/assets/{graph-CtTAhETf.js → graph-z0wljCJ_.js} +1 -1
- package/public/assets/index-CCsulXgB.css +1 -0
- package/public/assets/index-DplAQDze.js +1781 -0
- package/public/assets/index-iDg75qrw.js +23 -0
- package/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-G5NGCtYh.js} +1 -1
- package/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-BQ7qAaof.js} +1 -1
- package/{dist/public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → public/assets/kanban-definition-3W4ZIXB7-DnMJJT5X.js} +1 -1
- package/public/assets/{layout-DolajoeL.js → layout-CbJei3Fo.js} +1 -1
- package/public/assets/{linear-B5Hf7uIN.js → linear-3YD7PkfQ.js} +1 -1
- package/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-DjfX8Ewh.js} +5 -5
- package/public/assets/{min-UFvm8GLY.js → min-Chhh-zX-.js} +1 -1
- package/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-CLM5K3ub.js} +1 -1
- package/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BFiT_I_m.js} +1 -1
- package/{dist/public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → public/assets/quadrantDiagram-AYHSOK5B-Be7QQurL.js} +1 -1
- package/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-5NrHk2Xy.js} +1 -1
- package/{dist/public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → public/assets/sankeyDiagram-TZEHDZUN-DQ8Siqqh.js} +1 -1
- package/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-Bj_sOA9D.js} +1 -1
- package/{dist/public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → public/assets/stateDiagram-FKZM4ZOC-DncnQ-99.js} +1 -1
- package/public/assets/stateDiagram-v2-4FDKWEC3-BdQ5RGa6.js +1 -0
- package/{dist/public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → public/assets/timeline-definition-IT6M3QCI-C9uFv_GY.js} +1 -1
- package/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-CTzKLdTW.js} +1 -1
- package/{dist/public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → public/assets/xychartDiagram-PRI3JC2R-BP2VkXwS.js} +1 -1
- package/public/index.html +15 -2
- package/dist/public/assets/channel-CgkYY8Ci.js +0 -1
- package/dist/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
- package/dist/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
- package/dist/public/assets/clone-mkrd6lC9.js +0 -1
- package/dist/public/assets/index-Czr2q0NP.css +0 -1
- package/dist/public/assets/index-kEIJA0pC.js +0 -1781
- package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +0 -1
- package/public/assets/channel-CgkYY8Ci.js +0 -1
- package/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
- package/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
- package/public/assets/clone-mkrd6lC9.js +0 -1
- package/public/assets/index-Czr2q0NP.css +0 -1
- package/public/assets/index-kEIJA0pC.js +0 -1781
- 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
|
|
19
|
-
import { fileURLToPath } from "url";
|
|
18
|
+
import path10 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 {
|
|
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
|
-
/**
|
|
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
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sqlite.exec(`
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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";
|
|
150
349
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
}
|
|
154
414
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 {
|
|
159
589
|
}
|
|
160
|
-
} catch {
|
|
161
590
|
}
|
|
162
591
|
|
|
163
592
|
// src/amuxBridge.ts
|
|
164
|
-
import { randomUUID } from "crypto";
|
|
165
|
-
import
|
|
166
|
-
import
|
|
167
|
-
import {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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";
|
|
597
|
+
|
|
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 [];
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/amuxBridge.ts
|
|
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
|
-
|
|
173
|
-
async
|
|
694
|
+
const bridge = {
|
|
695
|
+
async getStreams(ids) {
|
|
174
696
|
if (ids.length === 0) return [];
|
|
175
|
-
return
|
|
697
|
+
return db.select().from(streams).where(inArray(streams.id, ids)).all();
|
|
176
698
|
},
|
|
177
|
-
async
|
|
178
|
-
return
|
|
699
|
+
async getStream(id) {
|
|
700
|
+
return db.select().from(streams).where(eq2(streams.id, id)).get() ?? null;
|
|
179
701
|
},
|
|
180
|
-
async
|
|
181
|
-
let configId = params.
|
|
702
|
+
async createStream(params) {
|
|
703
|
+
let configId = params.streamConfigId;
|
|
182
704
|
if (!configId) {
|
|
183
|
-
const lastUsedRow =
|
|
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 =
|
|
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 =
|
|
711
|
+
const firstConfig = db.select().from(streamConfigs).get();
|
|
190
712
|
if (!firstConfig) {
|
|
191
|
-
throw new Error("No
|
|
713
|
+
throw new Error("No stream configs available");
|
|
192
714
|
}
|
|
193
715
|
configId = firstConfig.id;
|
|
194
716
|
}
|
|
195
717
|
} else {
|
|
196
|
-
|
|
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 ??
|
|
720
|
+
const id = params.id ?? randomUUID2();
|
|
199
721
|
const now = /* @__PURE__ */ new Date();
|
|
200
|
-
const config =
|
|
201
|
-
const
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
|
736
|
+
return db.select().from(streams).where(eq2(streams.id, id)).get();
|
|
213
737
|
},
|
|
214
|
-
async
|
|
215
|
-
await
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
220
|
-
return
|
|
749
|
+
async getStreamConfigs() {
|
|
750
|
+
return db.select().from(streamConfigs).all();
|
|
221
751
|
},
|
|
222
|
-
async
|
|
223
|
-
|
|
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
|
|
226
|
-
|
|
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
|
|
229
|
-
|
|
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(
|
|
232
|
-
await
|
|
814
|
+
async cancel(streamId) {
|
|
815
|
+
await muxManager2.cancel(streamId);
|
|
233
816
|
},
|
|
234
|
-
async setMode(
|
|
235
|
-
await
|
|
817
|
+
async setMode(streamId, modeId) {
|
|
818
|
+
await muxManager2.setMode(streamId, modeId);
|
|
236
819
|
},
|
|
237
|
-
async setModel(
|
|
238
|
-
await
|
|
820
|
+
async setModel(streamId, modelId) {
|
|
821
|
+
await muxManager2.setModel(streamId, modelId);
|
|
239
822
|
},
|
|
240
|
-
respondPermission(
|
|
241
|
-
|
|
823
|
+
respondPermission(streamId, requestId, optionId) {
|
|
824
|
+
muxManager2.respondPermission(streamId, requestId, optionId);
|
|
242
825
|
},
|
|
243
|
-
|
|
826
|
+
subscribeToStream(streamId, callback) {
|
|
244
827
|
const handler = (event) => {
|
|
245
|
-
if (event.
|
|
246
|
-
callback(event
|
|
828
|
+
if (event.streamId === streamId) {
|
|
829
|
+
callback(event);
|
|
247
830
|
}
|
|
248
831
|
};
|
|
249
|
-
|
|
250
|
-
return () =>
|
|
832
|
+
muxManager2.on("update", handler);
|
|
833
|
+
return () => muxManager2.off("update", handler);
|
|
251
834
|
},
|
|
252
|
-
async listFilesForAutocomplete(
|
|
253
|
-
const
|
|
254
|
-
if (!
|
|
255
|
-
return listFilesForAutocomplete(
|
|
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(
|
|
259
|
-
|
|
841
|
+
terminalWrite(streamId, data) {
|
|
842
|
+
muxManager2.terminalWrite(streamId, data);
|
|
260
843
|
},
|
|
261
|
-
terminalResize(
|
|
262
|
-
|
|
844
|
+
terminalResize(streamId, cols, rows) {
|
|
845
|
+
muxManager2.terminalResize(streamId, cols, rows);
|
|
263
846
|
},
|
|
264
|
-
getTerminalScrollback(
|
|
265
|
-
return
|
|
847
|
+
getTerminalScrollback(streamId) {
|
|
848
|
+
return muxManager2.getTerminalScrollback(streamId);
|
|
266
849
|
},
|
|
267
|
-
|
|
268
|
-
return
|
|
850
|
+
isTerminalStream(streamId) {
|
|
851
|
+
return muxManager2.isTerminalStream(streamId);
|
|
269
852
|
},
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
285
|
-
import { eq as
|
|
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,
|
|
884
|
+
function findPanePath(node, paneId, path11 = []) {
|
|
301
885
|
if (node.type === "leaf") {
|
|
302
|
-
return node.paneId === paneId ?
|
|
886
|
+
return node.paneId === paneId ? path11 : null;
|
|
303
887
|
}
|
|
304
|
-
const firstPath = findPanePath(node.first, paneId, [...
|
|
888
|
+
const firstPath = findPanePath(node.first, paneId, [...path11, "first"]);
|
|
305
889
|
if (firstPath) return firstPath;
|
|
306
|
-
return findPanePath(node.second, paneId, [...
|
|
890
|
+
return findPanePath(node.second, paneId, [...path11, "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,
|
|
344
|
-
if (
|
|
927
|
+
function updateRatio(root, path11, newRatio) {
|
|
928
|
+
if (path11.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] =
|
|
935
|
+
const [next, ...rest] = path11;
|
|
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
|
|
360
|
-
if (!
|
|
943
|
+
const path11 = findPanePath(root, currentPaneId);
|
|
944
|
+
if (!path11) return null;
|
|
361
945
|
let node = root;
|
|
362
946
|
const pathNodes = [];
|
|
363
|
-
for (const step of
|
|
947
|
+
for (const step of path11) {
|
|
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
|
|
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(
|
|
415
|
-
const
|
|
416
|
-
allPanes.map((p2) => p2.
|
|
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
|
|
419
|
-
const
|
|
420
|
-
const
|
|
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
|
|
427
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1053
|
+
const configId = input.streamConfigId ?? input.agentConfigId;
|
|
1054
|
+
let stream = null;
|
|
1055
|
+
if (configId) {
|
|
1056
|
+
stream = await amux.createStream({
|
|
468
1057
|
directory,
|
|
469
|
-
|
|
1058
|
+
streamConfigId: configId
|
|
470
1059
|
});
|
|
471
1060
|
}
|
|
472
|
-
const windowId = input.id ??
|
|
473
|
-
const paneId =
|
|
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:
|
|
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(
|
|
490
|
-
const newPanes = db.select().from(panes).where(
|
|
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
|
-
|
|
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(
|
|
513
|
-
return db.select().from(windows).where(
|
|
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/
|
|
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(
|
|
1109
|
+
const windowPanes = db.select().from(panes).where(eq3(panes.windowId, input.id)).all();
|
|
520
1110
|
for (const pane of windowPanes) {
|
|
521
|
-
if (pane.
|
|
522
|
-
await amux.
|
|
1111
|
+
if (pane.eventStream) {
|
|
1112
|
+
await amux.deleteStream(pane.eventStream.streamId);
|
|
523
1113
|
}
|
|
524
1114
|
}
|
|
525
|
-
db.delete(windows).where(
|
|
526
|
-
const activeRow = db.select().from(uiState).where(
|
|
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(
|
|
1121
|
+
db.update(uiState).set({ value: newActiveId }).where(eq3(uiState.key, "active_window_id")).run();
|
|
532
1122
|
} else {
|
|
533
|
-
db.delete(uiState).where(
|
|
1123
|
+
db.delete(uiState).where(eq3(uiState.key, "active_window_id")).run();
|
|
534
1124
|
}
|
|
535
1125
|
}
|
|
536
1126
|
return { ok: true };
|
|
@@ -540,15 +1130,26 @@ 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(
|
|
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
|
|
551
|
-
import
|
|
1140
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1141
|
+
import * as path6 from "path";
|
|
1142
|
+
import * as os3 from "os";
|
|
1143
|
+
import { eq as eq4 } from "drizzle-orm";
|
|
1144
|
+
function expandTilde(p2) {
|
|
1145
|
+
if (p2.startsWith("~/")) {
|
|
1146
|
+
return path6.join(os3.homedir(), p2.slice(2));
|
|
1147
|
+
}
|
|
1148
|
+
if (p2 === "~") {
|
|
1149
|
+
return os3.homedir();
|
|
1150
|
+
}
|
|
1151
|
+
return p2;
|
|
1152
|
+
}
|
|
552
1153
|
var amux2;
|
|
553
1154
|
function setAmuxBridge2(bridge) {
|
|
554
1155
|
amux2 = bridge;
|
|
@@ -566,20 +1167,20 @@ var layoutRouter = router({
|
|
|
566
1167
|
newPaneId: z2.string().optional(),
|
|
567
1168
|
newSessionId: z2.string().optional()
|
|
568
1169
|
})).mutation(async ({ input }) => {
|
|
569
|
-
const sourcePane = db.select().from(panes).where(
|
|
1170
|
+
const sourcePane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
570
1171
|
if (!sourcePane) {
|
|
571
1172
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
572
1173
|
}
|
|
573
|
-
const window = db.select().from(windows).where(
|
|
1174
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
574
1175
|
if (!window) {
|
|
575
1176
|
throw new Error(`Window ${input.windowId} not found`);
|
|
576
1177
|
}
|
|
577
|
-
const
|
|
578
|
-
const newPaneId = input.newPaneId ?? randomUUID3();
|
|
1178
|
+
const newPaneId = input.newPaneId ?? randomUUID4();
|
|
579
1179
|
db.insert(panes).values({
|
|
580
1180
|
id: newPaneId,
|
|
581
1181
|
windowId: input.windowId,
|
|
582
|
-
content:
|
|
1182
|
+
content: null,
|
|
1183
|
+
eventStream: null
|
|
583
1184
|
}).run();
|
|
584
1185
|
const currentLayout = window.layout;
|
|
585
1186
|
const newLayout = splitPane(currentLayout, input.paneId, newPaneId, input.direction);
|
|
@@ -587,19 +1188,22 @@ var layoutRouter = router({
|
|
|
587
1188
|
layout: newLayout,
|
|
588
1189
|
activePaneId: newPaneId,
|
|
589
1190
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
590
|
-
}).where(
|
|
1191
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
591
1192
|
const now = /* @__PURE__ */ new Date();
|
|
592
1193
|
return {
|
|
593
1194
|
newPaneId,
|
|
594
|
-
|
|
1195
|
+
newStreamId: null,
|
|
595
1196
|
layout: newLayout,
|
|
596
1197
|
newPane: {
|
|
597
1198
|
id: newPaneId,
|
|
598
1199
|
windowId: input.windowId,
|
|
599
|
-
content:
|
|
1200
|
+
content: null,
|
|
1201
|
+
eventStream: null,
|
|
600
1202
|
createdAt: now.toISOString(),
|
|
601
|
-
|
|
602
|
-
}
|
|
1203
|
+
stream: null
|
|
1204
|
+
},
|
|
1205
|
+
// Legacy aliases
|
|
1206
|
+
newSessionId: null
|
|
603
1207
|
};
|
|
604
1208
|
}),
|
|
605
1209
|
/**
|
|
@@ -609,27 +1213,27 @@ var layoutRouter = router({
|
|
|
609
1213
|
windowId: z2.string(),
|
|
610
1214
|
paneId: z2.string()
|
|
611
1215
|
})).mutation(async ({ input }) => {
|
|
612
|
-
const window = db.select().from(windows).where(
|
|
1216
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
613
1217
|
if (!window) {
|
|
614
1218
|
throw new Error(`Window ${input.windowId} not found`);
|
|
615
1219
|
}
|
|
616
|
-
const pane = db.select().from(panes).where(
|
|
1220
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
617
1221
|
if (!pane) {
|
|
618
1222
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
619
1223
|
}
|
|
620
1224
|
const currentLayout = window.layout;
|
|
621
1225
|
const allPaneIds = getAllPaneIds(currentLayout);
|
|
622
1226
|
if (allPaneIds.length === 1) {
|
|
623
|
-
if (pane.
|
|
624
|
-
await amux2.
|
|
1227
|
+
if (pane.eventStream) {
|
|
1228
|
+
await amux2.deleteStream(pane.eventStream.streamId);
|
|
625
1229
|
}
|
|
626
|
-
db.delete(windows).where(
|
|
1230
|
+
db.delete(windows).where(eq4(windows.id, input.windowId)).run();
|
|
627
1231
|
return { windowClosed: true, layout: null, activePaneId: null };
|
|
628
1232
|
}
|
|
629
|
-
if (pane.
|
|
630
|
-
await amux2.
|
|
1233
|
+
if (pane.eventStream) {
|
|
1234
|
+
await amux2.deleteStream(pane.eventStream.streamId);
|
|
631
1235
|
}
|
|
632
|
-
db.delete(panes).where(
|
|
1236
|
+
db.delete(panes).where(eq4(panes.id, input.paneId)).run();
|
|
633
1237
|
const newLayout = removePane(currentLayout, input.paneId);
|
|
634
1238
|
if (!newLayout) {
|
|
635
1239
|
throw new Error("Failed to remove pane from layout");
|
|
@@ -639,7 +1243,7 @@ var layoutRouter = router({
|
|
|
639
1243
|
layout: newLayout,
|
|
640
1244
|
activePaneId: newActivePaneId,
|
|
641
1245
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
642
|
-
}).where(
|
|
1246
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
643
1247
|
return {
|
|
644
1248
|
windowClosed: false,
|
|
645
1249
|
layout: newLayout,
|
|
@@ -656,7 +1260,7 @@ var layoutRouter = router({
|
|
|
656
1260
|
db.update(windows).set({
|
|
657
1261
|
activePaneId: input.paneId,
|
|
658
1262
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
659
|
-
}).where(
|
|
1263
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
660
1264
|
return { ok: true };
|
|
661
1265
|
}),
|
|
662
1266
|
/**
|
|
@@ -667,7 +1271,7 @@ var layoutRouter = router({
|
|
|
667
1271
|
currentPaneId: z2.string(),
|
|
668
1272
|
direction: z2.enum(["left", "right", "up", "down"])
|
|
669
1273
|
})).mutation(async ({ input }) => {
|
|
670
|
-
const window = db.select().from(windows).where(
|
|
1274
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
671
1275
|
if (!window) {
|
|
672
1276
|
throw new Error(`Window ${input.windowId} not found`);
|
|
673
1277
|
}
|
|
@@ -677,7 +1281,7 @@ var layoutRouter = router({
|
|
|
677
1281
|
db.update(windows).set({
|
|
678
1282
|
activePaneId: adjacentPaneId,
|
|
679
1283
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
680
|
-
}).where(
|
|
1284
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
681
1285
|
return { newActivePaneId: adjacentPaneId };
|
|
682
1286
|
}
|
|
683
1287
|
return { newActivePaneId: null };
|
|
@@ -690,93 +1294,110 @@ var layoutRouter = router({
|
|
|
690
1294
|
path: z2.array(z2.enum(["first", "second"])),
|
|
691
1295
|
ratio: z2.number().min(10).max(90)
|
|
692
1296
|
})).mutation(async ({ input }) => {
|
|
693
|
-
const window = db.select().from(windows).where(
|
|
1297
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
694
1298
|
if (!window) {
|
|
695
1299
|
throw new Error(`Window ${input.windowId} not found`);
|
|
696
1300
|
}
|
|
697
1301
|
const currentLayout = window.layout;
|
|
698
1302
|
const newLayout = updateRatio(currentLayout, input.path, input.ratio);
|
|
699
|
-
db.update(windows).set({ layout: newLayout }).where(
|
|
1303
|
+
db.update(windows).set({ layout: newLayout }).where(eq4(windows.id, input.windowId)).run();
|
|
700
1304
|
return { layout: newLayout };
|
|
701
1305
|
}),
|
|
702
1306
|
/**
|
|
703
|
-
* Set
|
|
704
|
-
*
|
|
1307
|
+
* Set static content for a pane (browser URL, etc.).
|
|
1308
|
+
* Upsert behavior - works for both initial set and updates.
|
|
705
1309
|
*/
|
|
706
|
-
|
|
1310
|
+
setPaneContent: publicProcedure.input(z2.object({
|
|
707
1311
|
paneId: z2.string(),
|
|
708
|
-
|
|
709
|
-
|
|
1312
|
+
content: z2.record(z2.unknown()).nullable(),
|
|
1313
|
+
rendererId: z2.string().optional()
|
|
710
1314
|
})).mutation(async ({ input }) => {
|
|
711
|
-
const pane = db.select().from(panes).where(
|
|
1315
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
712
1316
|
if (!pane) {
|
|
713
1317
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
714
1318
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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.
|
|
740
|
-
*/
|
|
741
|
-
setBrowserContent: publicProcedure.input(z2.object({
|
|
742
|
-
paneId: z2.string(),
|
|
743
|
-
url: z2.string().url()
|
|
744
|
-
})).mutation(async ({ input }) => {
|
|
745
|
-
const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
|
|
746
|
-
if (!pane) {
|
|
747
|
-
throw new Error(`Pane ${input.paneId} not found`);
|
|
748
|
-
}
|
|
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 };
|
|
1319
|
+
db.update(panes).set({
|
|
1320
|
+
content: input.content,
|
|
1321
|
+
rendererId: input.rendererId ?? null
|
|
1322
|
+
}).where(eq4(panes.id, input.paneId)).run();
|
|
1323
|
+
return { content: input.content, rendererId: input.rendererId };
|
|
755
1324
|
}),
|
|
756
1325
|
/**
|
|
757
|
-
*
|
|
1326
|
+
* Set an event stream for a pane (agent or shell).
|
|
1327
|
+
* Creates stream in amux and starts the backend.
|
|
758
1328
|
*/
|
|
759
|
-
|
|
1329
|
+
setPaneEventStream: publicProcedure.input(z2.object({
|
|
760
1330
|
paneId: z2.string(),
|
|
761
|
-
|
|
1331
|
+
streamConfigId: z2.string(),
|
|
1332
|
+
directory: z2.string().optional(),
|
|
1333
|
+
rendererId: z2.string().optional(),
|
|
1334
|
+
// Legacy alias
|
|
1335
|
+
agentConfigId: z2.string().optional()
|
|
762
1336
|
})).mutation(async ({ input }) => {
|
|
763
|
-
const pane = db.select().from(panes).where(
|
|
1337
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
764
1338
|
if (!pane) {
|
|
765
1339
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
766
1340
|
}
|
|
767
|
-
if (pane.
|
|
768
|
-
throw new Error(`Pane ${input.paneId}
|
|
1341
|
+
if (pane.eventStream) {
|
|
1342
|
+
throw new Error(`Pane ${input.paneId} already has an event stream`);
|
|
769
1343
|
}
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
1344
|
+
const configId = input.streamConfigId ?? input.agentConfigId;
|
|
1345
|
+
const directory = input.directory ? expandTilde(input.directory) : process.cwd();
|
|
1346
|
+
const stream = await amux2.createStream({
|
|
1347
|
+
directory,
|
|
1348
|
+
streamConfigId: configId
|
|
1349
|
+
});
|
|
1350
|
+
const eventStream = { type: stream.streamType, streamId: stream.id };
|
|
1351
|
+
db.update(panes).set({
|
|
1352
|
+
eventStream,
|
|
1353
|
+
rendererId: input.rendererId ?? null
|
|
1354
|
+
}).where(eq4(panes.id, input.paneId)).run();
|
|
1355
|
+
await amux2.startStream(stream.id);
|
|
1356
|
+
return {
|
|
1357
|
+
streamId: stream.id,
|
|
1358
|
+
eventStream,
|
|
1359
|
+
stream: {
|
|
1360
|
+
id: stream.id,
|
|
1361
|
+
directory: stream.directory,
|
|
1362
|
+
streamConfigId: stream.streamConfigId,
|
|
1363
|
+
streamType: stream.streamType,
|
|
1364
|
+
model: stream.model,
|
|
1365
|
+
mode: stream.mode,
|
|
1366
|
+
createdAt: stream.createdAt.toISOString()
|
|
1367
|
+
},
|
|
1368
|
+
// Legacy aliases
|
|
1369
|
+
sessionId: stream.id,
|
|
1370
|
+
process: eventStream,
|
|
1371
|
+
session: {
|
|
1372
|
+
id: stream.id,
|
|
1373
|
+
directory: stream.directory,
|
|
1374
|
+
agentConfigId: stream.streamConfigId,
|
|
1375
|
+
model: stream.model,
|
|
1376
|
+
mode: stream.mode,
|
|
1377
|
+
createdAt: stream.createdAt.toISOString()
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
773
1380
|
})
|
|
774
1381
|
});
|
|
775
1382
|
|
|
776
1383
|
// src/trpc/agents.ts
|
|
777
1384
|
import { z as z3 } from "zod";
|
|
778
|
-
import { eq as eq4 } from "drizzle-orm";
|
|
779
1385
|
import { observable } from "@trpc/server/observable";
|
|
1386
|
+
|
|
1387
|
+
// src/lib/panes.ts
|
|
1388
|
+
import { eq as eq5 } from "drizzle-orm";
|
|
1389
|
+
function getStreamIdForPane(paneId) {
|
|
1390
|
+
const pane = db.select().from(panes).where(eq5(panes.id, paneId)).get();
|
|
1391
|
+
if (!pane) {
|
|
1392
|
+
throw new Error(`Pane ${paneId} not found`);
|
|
1393
|
+
}
|
|
1394
|
+
if (!pane.eventStream) {
|
|
1395
|
+
throw new Error(`Pane ${paneId} does not have an event stream`);
|
|
1396
|
+
}
|
|
1397
|
+
return pane.eventStream.streamId;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/trpc/agents.ts
|
|
780
1401
|
var amux3;
|
|
781
1402
|
function setAmuxBridge3(bridge) {
|
|
782
1403
|
amux3 = bridge;
|
|
@@ -784,71 +1405,61 @@ function setAmuxBridge3(bridge) {
|
|
|
784
1405
|
var paneInput = z3.object({
|
|
785
1406
|
paneId: z3.string()
|
|
786
1407
|
});
|
|
787
|
-
|
|
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({
|
|
1408
|
+
var streamsRouter = router({
|
|
798
1409
|
/**
|
|
799
|
-
* Start
|
|
1410
|
+
* Start stream for a pane.
|
|
800
1411
|
*/
|
|
801
1412
|
start: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
802
|
-
const
|
|
803
|
-
return amux3.
|
|
1413
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1414
|
+
return amux3.startStream(streamId);
|
|
804
1415
|
}),
|
|
805
1416
|
/**
|
|
806
|
-
* Stop
|
|
1417
|
+
* Stop stream for a pane.
|
|
807
1418
|
*/
|
|
808
1419
|
stop: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
809
|
-
const
|
|
810
|
-
await amux3.
|
|
1420
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1421
|
+
await amux3.stopStream(streamId);
|
|
811
1422
|
return { ok: true };
|
|
812
1423
|
}),
|
|
813
1424
|
/**
|
|
814
|
-
* Send
|
|
1425
|
+
* Send input to a pane's stream.
|
|
815
1426
|
*/
|
|
816
|
-
|
|
1427
|
+
input: publicProcedure.input(z3.object({
|
|
817
1428
|
paneId: z3.string(),
|
|
818
1429
|
message: z3.string()
|
|
819
1430
|
})).mutation(async ({ input }) => {
|
|
820
|
-
const
|
|
821
|
-
await amux3.
|
|
1431
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1432
|
+
await amux3.input(streamId, input.message);
|
|
822
1433
|
return { ok: true };
|
|
823
1434
|
}),
|
|
824
1435
|
/**
|
|
825
1436
|
* Cancel the current prompt.
|
|
826
1437
|
*/
|
|
827
1438
|
cancel: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
828
|
-
const
|
|
829
|
-
await amux3.cancel(
|
|
1439
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1440
|
+
await amux3.cancel(streamId);
|
|
830
1441
|
return { ok: true };
|
|
831
1442
|
}),
|
|
832
1443
|
/**
|
|
833
|
-
* Set
|
|
1444
|
+
* Set stream mode.
|
|
834
1445
|
*/
|
|
835
1446
|
setMode: publicProcedure.input(z3.object({
|
|
836
1447
|
paneId: z3.string(),
|
|
837
1448
|
modeId: z3.string()
|
|
838
1449
|
})).mutation(async ({ input }) => {
|
|
839
|
-
const
|
|
840
|
-
await amux3.setMode(
|
|
1450
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1451
|
+
await amux3.setMode(streamId, input.modeId);
|
|
841
1452
|
return { ok: true };
|
|
842
1453
|
}),
|
|
843
1454
|
/**
|
|
844
|
-
* Set
|
|
1455
|
+
* Set stream model.
|
|
845
1456
|
*/
|
|
846
1457
|
setModel: publicProcedure.input(z3.object({
|
|
847
1458
|
paneId: z3.string(),
|
|
848
1459
|
modelId: z3.string()
|
|
849
1460
|
})).mutation(async ({ input }) => {
|
|
850
|
-
const
|
|
851
|
-
await amux3.setModel(
|
|
1461
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1462
|
+
await amux3.setModel(streamId, input.modelId);
|
|
852
1463
|
return { ok: true };
|
|
853
1464
|
}),
|
|
854
1465
|
/**
|
|
@@ -859,17 +1470,17 @@ var agentsRouter = router({
|
|
|
859
1470
|
requestId: z3.string(),
|
|
860
1471
|
optionId: z3.string()
|
|
861
1472
|
})).mutation(async ({ input }) => {
|
|
862
|
-
const
|
|
863
|
-
amux3.respondPermission(
|
|
1473
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1474
|
+
amux3.respondPermission(streamId, input.requestId, input.optionId);
|
|
864
1475
|
return { ok: true };
|
|
865
1476
|
}),
|
|
866
1477
|
/**
|
|
867
|
-
* Subscribe to updates for a pane's
|
|
1478
|
+
* Subscribe to updates for a pane's stream.
|
|
868
1479
|
*/
|
|
869
1480
|
subscribe: publicProcedure.input(paneInput).subscription(({ input }) => {
|
|
870
|
-
const
|
|
1481
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
871
1482
|
return observable((emit) => {
|
|
872
|
-
return amux3.
|
|
1483
|
+
return amux3.subscribeToStream(streamId, (update) => {
|
|
873
1484
|
emit.next(update);
|
|
874
1485
|
});
|
|
875
1486
|
});
|
|
@@ -878,22 +1489,42 @@ var agentsRouter = router({
|
|
|
878
1489
|
|
|
879
1490
|
// src/trpc/files.ts
|
|
880
1491
|
import { z as z4 } from "zod";
|
|
881
|
-
import
|
|
1492
|
+
import * as fs6 from "fs/promises";
|
|
1493
|
+
import * as path7 from "path";
|
|
1494
|
+
import * as os4 from "os";
|
|
1495
|
+
function expandTilde2(p2) {
|
|
1496
|
+
if (p2.startsWith("~/")) {
|
|
1497
|
+
return path7.join(os4.homedir(), p2.slice(2));
|
|
1498
|
+
}
|
|
1499
|
+
if (p2 === "~") {
|
|
1500
|
+
return os4.homedir();
|
|
1501
|
+
}
|
|
1502
|
+
return p2;
|
|
1503
|
+
}
|
|
882
1504
|
var amux4;
|
|
883
1505
|
function setAmuxBridge4(bridge) {
|
|
884
1506
|
amux4 = bridge;
|
|
885
1507
|
}
|
|
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
1508
|
var filesRouter = router({
|
|
1509
|
+
/**
|
|
1510
|
+
* Get server's current working directory.
|
|
1511
|
+
*/
|
|
1512
|
+
getCwd: publicProcedure.query(() => process.cwd()),
|
|
1513
|
+
/**
|
|
1514
|
+
* List directories in a given path.
|
|
1515
|
+
* Used by DirectoryPicker for browsing.
|
|
1516
|
+
*/
|
|
1517
|
+
listDirectory: publicProcedure.input(z4.object({
|
|
1518
|
+
directory: z4.string()
|
|
1519
|
+
})).query(async ({ input }) => {
|
|
1520
|
+
try {
|
|
1521
|
+
const absPath = path7.resolve(expandTilde2(input.directory));
|
|
1522
|
+
const entries = await fs6.readdir(absPath, { withFileTypes: true });
|
|
1523
|
+
return entries.filter((e) => e.isDirectory()).map((e) => ({ name: e.name, isDirectory: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1524
|
+
} catch {
|
|
1525
|
+
return [];
|
|
1526
|
+
}
|
|
1527
|
+
}),
|
|
897
1528
|
/**
|
|
898
1529
|
* List files/directories matching a partial path for autocomplete.
|
|
899
1530
|
* Returns files in the session's working directory.
|
|
@@ -903,8 +1534,8 @@ var filesRouter = router({
|
|
|
903
1534
|
partialPath: z4.string(),
|
|
904
1535
|
limit: z4.number().default(20)
|
|
905
1536
|
})).query(async ({ input }) => {
|
|
906
|
-
const
|
|
907
|
-
return amux4.listFilesForAutocomplete(
|
|
1537
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1538
|
+
return amux4.listFilesForAutocomplete(streamId, input.partialPath, input.limit);
|
|
908
1539
|
})
|
|
909
1540
|
});
|
|
910
1541
|
|
|
@@ -918,15 +1549,15 @@ function setAmuxBridge5(bridge) {
|
|
|
918
1549
|
var paneInput2 = z5.object({
|
|
919
1550
|
paneId: z5.string()
|
|
920
1551
|
});
|
|
921
|
-
function
|
|
1552
|
+
function getStreamIdForPane2(paneId) {
|
|
922
1553
|
const pane = db.select().from(panes).where(eq6(panes.id, paneId)).get();
|
|
923
1554
|
if (!pane) {
|
|
924
1555
|
throw new Error(`Pane ${paneId} not found`);
|
|
925
1556
|
}
|
|
926
|
-
if (pane.
|
|
927
|
-
throw new Error(`Pane ${paneId}
|
|
1557
|
+
if (!pane.eventStream) {
|
|
1558
|
+
throw new Error(`Pane ${paneId} does not have an event stream`);
|
|
928
1559
|
}
|
|
929
|
-
return pane.
|
|
1560
|
+
return pane.eventStream.streamId;
|
|
930
1561
|
}
|
|
931
1562
|
var terminalsRouter = router({
|
|
932
1563
|
/**
|
|
@@ -938,8 +1569,8 @@ var terminalsRouter = router({
|
|
|
938
1569
|
data: z5.string()
|
|
939
1570
|
})
|
|
940
1571
|
).mutation(({ input }) => {
|
|
941
|
-
const
|
|
942
|
-
amux5.terminalWrite(
|
|
1572
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1573
|
+
amux5.terminalWrite(streamId, input.data);
|
|
943
1574
|
return { ok: true };
|
|
944
1575
|
}),
|
|
945
1576
|
/**
|
|
@@ -952,16 +1583,16 @@ var terminalsRouter = router({
|
|
|
952
1583
|
rows: z5.number().int().positive()
|
|
953
1584
|
})
|
|
954
1585
|
).mutation(({ input }) => {
|
|
955
|
-
const
|
|
956
|
-
amux5.terminalResize(
|
|
1586
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1587
|
+
amux5.terminalResize(streamId, input.cols, input.rows);
|
|
957
1588
|
return { ok: true };
|
|
958
1589
|
}),
|
|
959
1590
|
/**
|
|
960
|
-
* Check if a pane is a terminal
|
|
1591
|
+
* Check if a pane is a terminal stream.
|
|
961
1592
|
*/
|
|
962
1593
|
isTerminal: publicProcedure.input(paneInput2).query(({ input }) => {
|
|
963
|
-
const
|
|
964
|
-
return { isTerminal: amux5.
|
|
1594
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1595
|
+
return { isTerminal: amux5.isTerminalStream(streamId) };
|
|
965
1596
|
}),
|
|
966
1597
|
/**
|
|
967
1598
|
* Set terminal title (from OSC escape sequence).
|
|
@@ -970,12 +1601,264 @@ var terminalsRouter = router({
|
|
|
970
1601
|
paneId: z5.string(),
|
|
971
1602
|
title: z5.string()
|
|
972
1603
|
})).mutation(({ input }) => {
|
|
973
|
-
const
|
|
974
|
-
amux5.
|
|
1604
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1605
|
+
amux5.updateStreamTitle(streamId, input.title);
|
|
975
1606
|
return { ok: true };
|
|
976
1607
|
})
|
|
977
1608
|
});
|
|
978
1609
|
|
|
1610
|
+
// src/trpc/renderers.ts
|
|
1611
|
+
import { observable as observable2 } from "@trpc/server/observable";
|
|
1612
|
+
|
|
1613
|
+
// src/lib/rendererWatcher.ts
|
|
1614
|
+
import chokidar2 from "chokidar";
|
|
1615
|
+
import * as path9 from "path";
|
|
1616
|
+
import * as os5 from "os";
|
|
1617
|
+
import * as fs8 from "fs/promises";
|
|
1618
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
1619
|
+
|
|
1620
|
+
// src/lib/rendererCompiler.ts
|
|
1621
|
+
import { build as build2 } from "esbuild";
|
|
1622
|
+
import * as fs7 from "fs/promises";
|
|
1623
|
+
import * as path8 from "path";
|
|
1624
|
+
async function compileRenderer(pluginDir) {
|
|
1625
|
+
const metaPath = path8.join(pluginDir, "meta.json");
|
|
1626
|
+
const entryPath = path8.join(pluginDir, "index.tsx");
|
|
1627
|
+
const pkgPath = path8.join(pluginDir, "package.json");
|
|
1628
|
+
let meta;
|
|
1629
|
+
try {
|
|
1630
|
+
const metaContent = await fs7.readFile(metaPath, "utf-8");
|
|
1631
|
+
meta = JSON.parse(metaContent);
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`Failed to read meta file for ${pluginDir}. Expected ${metaPath} with { name, target } properties. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
if (!meta.name || typeof meta.name !== "string") {
|
|
1638
|
+
throw new Error(`Meta file ${metaPath} must have a 'name' string property`);
|
|
1639
|
+
}
|
|
1640
|
+
if (!meta.target || !meta.target.kind) {
|
|
1641
|
+
throw new Error(`Meta file ${metaPath} must have a 'target' object with 'kind' property`);
|
|
1642
|
+
}
|
|
1643
|
+
let hasPackageJson = false;
|
|
1644
|
+
try {
|
|
1645
|
+
const pkgContent = await fs7.readFile(pkgPath, "utf-8");
|
|
1646
|
+
const pkg = JSON.parse(pkgContent);
|
|
1647
|
+
hasPackageJson = true;
|
|
1648
|
+
const deps = pkg.dependencies || {};
|
|
1649
|
+
if (deps["react"] || deps["react-dom"]) {
|
|
1650
|
+
throw new Error(
|
|
1651
|
+
`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.`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
if (err instanceof Error && err.message.includes("peerDependencies")) {
|
|
1656
|
+
throw err;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
const result = await build2({
|
|
1660
|
+
entryPoints: [entryPath],
|
|
1661
|
+
bundle: true,
|
|
1662
|
+
write: false,
|
|
1663
|
+
format: "esm",
|
|
1664
|
+
jsx: "automatic",
|
|
1665
|
+
target: "es2020",
|
|
1666
|
+
external: ["react", "react-dom", "react/jsx-runtime", "@bytespell/shella-sdk", "@bytespell/shella-sdk/components/ui"],
|
|
1667
|
+
// If plugin has package.json, look for node_modules
|
|
1668
|
+
nodePaths: hasPackageJson ? [path8.join(pluginDir, "node_modules")] : [],
|
|
1669
|
+
minify: false
|
|
1670
|
+
});
|
|
1671
|
+
const code = result.outputFiles[0].text;
|
|
1672
|
+
const id = `user:${path8.basename(pluginDir)}`;
|
|
1673
|
+
return {
|
|
1674
|
+
id,
|
|
1675
|
+
name: meta.name,
|
|
1676
|
+
description: meta.description,
|
|
1677
|
+
target: meta.target,
|
|
1678
|
+
code
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
async function isValidPlugin(dirPath) {
|
|
1682
|
+
try {
|
|
1683
|
+
await fs7.access(path8.join(dirPath, "meta.json"));
|
|
1684
|
+
await fs7.access(path8.join(dirPath, "index.tsx"));
|
|
1685
|
+
return true;
|
|
1686
|
+
} catch {
|
|
1687
|
+
return false;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/lib/rendererWatcher.ts
|
|
1692
|
+
var RendererEventEmitter = class extends EventEmitter2 {
|
|
1693
|
+
emit(event, data) {
|
|
1694
|
+
return super.emit(event, data);
|
|
1695
|
+
}
|
|
1696
|
+
on(event, listener) {
|
|
1697
|
+
return super.on(event, listener);
|
|
1698
|
+
}
|
|
1699
|
+
off(event, listener) {
|
|
1700
|
+
return super.off(event, listener);
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
function watchRenderers(emitter) {
|
|
1704
|
+
const userPath = path9.join(os5.homedir(), ".shella", "renderers");
|
|
1705
|
+
ensureDir2(userPath);
|
|
1706
|
+
const knownPlugins = /* @__PURE__ */ new Map();
|
|
1707
|
+
const pendingCompiles = /* @__PURE__ */ new Map();
|
|
1708
|
+
const watcher = chokidar2.watch(userPath, {
|
|
1709
|
+
ignoreInitial: false,
|
|
1710
|
+
persistent: true,
|
|
1711
|
+
awaitWriteFinish: {
|
|
1712
|
+
stabilityThreshold: 100,
|
|
1713
|
+
pollInterval: 50
|
|
1714
|
+
},
|
|
1715
|
+
depth: 3
|
|
1716
|
+
// Watch inside plugin folders for nested components
|
|
1717
|
+
});
|
|
1718
|
+
const getPluginDir = (filePath) => {
|
|
1719
|
+
const relative3 = path9.relative(userPath, filePath);
|
|
1720
|
+
const parts = relative3.split(path9.sep);
|
|
1721
|
+
if (parts.length >= 1 && parts[0]) {
|
|
1722
|
+
return path9.join(userPath, parts[0]);
|
|
1723
|
+
}
|
|
1724
|
+
return null;
|
|
1725
|
+
};
|
|
1726
|
+
const scheduleCompile = (pluginDir) => {
|
|
1727
|
+
if (pendingCompiles.has(pluginDir)) {
|
|
1728
|
+
clearTimeout(pendingCompiles.get(pluginDir));
|
|
1729
|
+
}
|
|
1730
|
+
pendingCompiles.set(
|
|
1731
|
+
pluginDir,
|
|
1732
|
+
setTimeout(async () => {
|
|
1733
|
+
pendingCompiles.delete(pluginDir);
|
|
1734
|
+
await handlePluginChange(pluginDir);
|
|
1735
|
+
}, 100)
|
|
1736
|
+
);
|
|
1737
|
+
};
|
|
1738
|
+
const handlePluginChange = async (pluginDir) => {
|
|
1739
|
+
if (!await isValidPlugin(pluginDir)) return;
|
|
1740
|
+
try {
|
|
1741
|
+
const compiled = await compileRenderer(pluginDir);
|
|
1742
|
+
if (!knownPlugins.has(pluginDir)) {
|
|
1743
|
+
knownPlugins.set(pluginDir, true);
|
|
1744
|
+
console.log(`[renderers] Added: ${compiled.id}`);
|
|
1745
|
+
emitter.emit("renderer:added", compiled);
|
|
1746
|
+
} else {
|
|
1747
|
+
console.log(`[renderers] Updated: ${compiled.id}`);
|
|
1748
|
+
emitter.emit("renderer:updated", { id: compiled.id, code: compiled.code });
|
|
1749
|
+
}
|
|
1750
|
+
} catch (err) {
|
|
1751
|
+
console.error(`[renderers] Error compiling ${pluginDir}:`, err);
|
|
1752
|
+
emitter.emit("renderer:error", {
|
|
1753
|
+
filePath: pluginDir,
|
|
1754
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
watcher.on("add", (filePath) => {
|
|
1759
|
+
const pluginDir = getPluginDir(filePath);
|
|
1760
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
1761
|
+
});
|
|
1762
|
+
watcher.on("change", (filePath) => {
|
|
1763
|
+
const pluginDir = getPluginDir(filePath);
|
|
1764
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
1765
|
+
});
|
|
1766
|
+
watcher.on("unlinkDir", (dirPath) => {
|
|
1767
|
+
if (knownPlugins.has(dirPath)) {
|
|
1768
|
+
const id = `user:${path9.basename(dirPath)}`;
|
|
1769
|
+
knownPlugins.delete(dirPath);
|
|
1770
|
+
console.log(`[renderers] Removed: ${id}`);
|
|
1771
|
+
emitter.emit("renderer:removed", { id });
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
watcher.on("error", (err) => {
|
|
1775
|
+
console.error("[renderers] Watcher error:", err);
|
|
1776
|
+
});
|
|
1777
|
+
return () => {
|
|
1778
|
+
for (const timeout of pendingCompiles.values()) {
|
|
1779
|
+
clearTimeout(timeout);
|
|
1780
|
+
}
|
|
1781
|
+
pendingCompiles.clear();
|
|
1782
|
+
watcher.close();
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
async function ensureDir2(dirPath) {
|
|
1786
|
+
try {
|
|
1787
|
+
await fs8.mkdir(dirPath, { recursive: true });
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
async function scanRenderers() {
|
|
1792
|
+
const userPath = path9.join(os5.homedir(), ".shella", "renderers");
|
|
1793
|
+
const renderers = [];
|
|
1794
|
+
try {
|
|
1795
|
+
const entries = await fs8.readdir(userPath, { withFileTypes: true });
|
|
1796
|
+
for (const entry of entries) {
|
|
1797
|
+
if (!entry.isDirectory()) continue;
|
|
1798
|
+
const pluginDir = path9.join(userPath, entry.name);
|
|
1799
|
+
if (await isValidPlugin(pluginDir)) {
|
|
1800
|
+
try {
|
|
1801
|
+
const compiled = await compileRenderer(pluginDir);
|
|
1802
|
+
renderers.push(compiled);
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
console.error(`[renderers] Error compiling ${pluginDir}:`, err);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
return renderers;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// src/trpc/renderers.ts
|
|
1814
|
+
var rendererEmitter = new RendererEventEmitter();
|
|
1815
|
+
var stopWatcher = null;
|
|
1816
|
+
function initRendererWatcher() {
|
|
1817
|
+
if (stopWatcher) {
|
|
1818
|
+
stopWatcher();
|
|
1819
|
+
}
|
|
1820
|
+
stopWatcher = watchRenderers(rendererEmitter);
|
|
1821
|
+
}
|
|
1822
|
+
var renderersRouter = router({
|
|
1823
|
+
/**
|
|
1824
|
+
* List all currently available user renderers
|
|
1825
|
+
* Used for initial hydration when client connects.
|
|
1826
|
+
*/
|
|
1827
|
+
list: publicProcedure.query(async () => {
|
|
1828
|
+
return scanRenderers();
|
|
1829
|
+
}),
|
|
1830
|
+
/**
|
|
1831
|
+
* Subscribe to renderer additions
|
|
1832
|
+
*/
|
|
1833
|
+
onAdded: publicProcedure.subscription(() => {
|
|
1834
|
+
return observable2((emit) => {
|
|
1835
|
+
const handler = (data) => emit.next(data);
|
|
1836
|
+
rendererEmitter.on("renderer:added", handler);
|
|
1837
|
+
return () => rendererEmitter.off("renderer:added", handler);
|
|
1838
|
+
});
|
|
1839
|
+
}),
|
|
1840
|
+
/**
|
|
1841
|
+
* Subscribe to renderer updates (code changes)
|
|
1842
|
+
*/
|
|
1843
|
+
onUpdated: publicProcedure.subscription(() => {
|
|
1844
|
+
return observable2((emit) => {
|
|
1845
|
+
const handler = (data) => emit.next(data);
|
|
1846
|
+
rendererEmitter.on("renderer:updated", handler);
|
|
1847
|
+
return () => rendererEmitter.off("renderer:updated", handler);
|
|
1848
|
+
});
|
|
1849
|
+
}),
|
|
1850
|
+
/**
|
|
1851
|
+
* Subscribe to renderer removals
|
|
1852
|
+
*/
|
|
1853
|
+
onRemoved: publicProcedure.subscription(() => {
|
|
1854
|
+
return observable2((emit) => {
|
|
1855
|
+
const handler = (data) => emit.next(data);
|
|
1856
|
+
rendererEmitter.on("renderer:removed", handler);
|
|
1857
|
+
return () => rendererEmitter.off("renderer:removed", handler);
|
|
1858
|
+
});
|
|
1859
|
+
})
|
|
1860
|
+
});
|
|
1861
|
+
|
|
979
1862
|
// src/trpc/router.ts
|
|
980
1863
|
function initializeRouters(amux6) {
|
|
981
1864
|
setAmuxBridge(amux6);
|
|
@@ -983,39 +1866,60 @@ function initializeRouters(amux6) {
|
|
|
983
1866
|
setAmuxBridge3(amux6);
|
|
984
1867
|
setAmuxBridge4(amux6);
|
|
985
1868
|
setAmuxBridge5(amux6);
|
|
1869
|
+
initRendererWatcher();
|
|
986
1870
|
}
|
|
987
1871
|
var shellaRouter = router({
|
|
988
1872
|
windows: windowsRouter,
|
|
989
1873
|
layout: layoutRouter,
|
|
990
|
-
|
|
1874
|
+
streams: streamsRouter,
|
|
991
1875
|
files: filesRouter,
|
|
992
|
-
terminals: terminalsRouter
|
|
1876
|
+
terminals: terminalsRouter,
|
|
1877
|
+
renderers: renderersRouter,
|
|
1878
|
+
// Legacy alias
|
|
1879
|
+
agents: streamsRouter
|
|
993
1880
|
});
|
|
994
1881
|
|
|
995
1882
|
// src/index.ts
|
|
996
|
-
|
|
1883
|
+
discoverAndRegisterConfigs(builtInDiscoverers);
|
|
1884
|
+
var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
|
|
997
1885
|
function createShellaServer(options = {}) {
|
|
998
1886
|
const port = options.port ?? 3067;
|
|
999
1887
|
if (options.verbose) {
|
|
1000
1888
|
setVerbose(true);
|
|
1001
1889
|
}
|
|
1890
|
+
const driverEmitter = new DriverEventEmitter();
|
|
1891
|
+
let stopDriverWatcher = null;
|
|
1892
|
+
driverEmitter.on("driver:added", ({ id, name }) => {
|
|
1893
|
+
debug("shella", `Driver registered: ${id} (${name})`);
|
|
1894
|
+
});
|
|
1895
|
+
driverEmitter.on("driver:updated", ({ id, name }) => {
|
|
1896
|
+
debug("shella", `Driver updated: ${id} (${name})`);
|
|
1897
|
+
});
|
|
1898
|
+
driverEmitter.on("driver:removed", ({ id }) => {
|
|
1899
|
+
debug("shella", `Driver removed: ${id}`);
|
|
1900
|
+
});
|
|
1901
|
+
driverEmitter.on("driver:error", ({ pluginDir, error }) => {
|
|
1902
|
+
console.error(`Driver error in ${pluginDir}:`, error.message);
|
|
1903
|
+
});
|
|
1002
1904
|
const amuxBridge = createAmuxBridge();
|
|
1003
1905
|
initializeRouters(amuxBridge);
|
|
1004
1906
|
const app = express();
|
|
1005
1907
|
app.use(express.json());
|
|
1006
|
-
const publicPath =
|
|
1908
|
+
const publicPath = path10.join(__dirname2, "..", "public");
|
|
1007
1909
|
app.use(express.static(publicPath));
|
|
1008
1910
|
app.use("/trpc", createExpressMiddleware({ router: shellaRouter }));
|
|
1009
1911
|
app.get("/health", (_req, res) => res.json({ status: "ok" }));
|
|
1010
1912
|
app.get("/{*splat}", (_req, res) => {
|
|
1011
|
-
res.sendFile(
|
|
1913
|
+
res.sendFile(path10.join(publicPath, "index.html"));
|
|
1012
1914
|
});
|
|
1013
1915
|
const server = createServer(app);
|
|
1014
1916
|
const wss = new WebSocketServer({ server, path: "/trpc" });
|
|
1015
1917
|
applyWSSHandler({ wss, router: shellaRouter });
|
|
1016
1918
|
return {
|
|
1017
|
-
start: () => {
|
|
1018
|
-
|
|
1919
|
+
start: async () => {
|
|
1920
|
+
await scanDrivers();
|
|
1921
|
+
stopDriverWatcher = watchDrivers(driverEmitter);
|
|
1922
|
+
return new Promise((resolve4, reject) => {
|
|
1019
1923
|
server.once("error", (err) => {
|
|
1020
1924
|
if (err.code === "EADDRINUSE") {
|
|
1021
1925
|
reject(new Error(`Port ${port} is already in use. Kill the other process or use a different port.`));
|
|
@@ -1027,14 +1931,15 @@ function createShellaServer(options = {}) {
|
|
|
1027
1931
|
const url = `http://localhost:${port}`;
|
|
1028
1932
|
debug("shella", `Running on ${url}`);
|
|
1029
1933
|
options.onReady?.(url);
|
|
1030
|
-
|
|
1934
|
+
resolve4();
|
|
1031
1935
|
});
|
|
1032
1936
|
});
|
|
1033
1937
|
},
|
|
1034
1938
|
stop: async () => {
|
|
1035
1939
|
debug("shella", "Shutting down...");
|
|
1940
|
+
stopDriverWatcher?.();
|
|
1036
1941
|
wss.close();
|
|
1037
|
-
await
|
|
1942
|
+
await muxManager3.stopAll();
|
|
1038
1943
|
server.close();
|
|
1039
1944
|
}
|
|
1040
1945
|
};
|