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