hotsheet 0.17.0-beta.23 → 0.17.0-beta.24
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/channel.js +177 -39
- package/dist/cli.js +256 -150
- package/dist/client/app.global.js +91 -91
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -34145,9 +34145,9 @@ var StdioServerTransport = class {
|
|
|
34145
34145
|
|
|
34146
34146
|
// src/channel.ts
|
|
34147
34147
|
init_zod();
|
|
34148
|
-
import { readFileSync as
|
|
34148
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
34149
34149
|
import { createServer } from "http";
|
|
34150
|
-
import { join as
|
|
34150
|
+
import { join as join4 } from "path";
|
|
34151
34151
|
|
|
34152
34152
|
// src/channel.tools.ts
|
|
34153
34153
|
init_zod();
|
|
@@ -34685,34 +34685,16 @@ async function callTool(name, args2, dataDir2, fetchFn = globalThis.fetch) {
|
|
|
34685
34685
|
}
|
|
34686
34686
|
|
|
34687
34687
|
// src/channel-config.ts
|
|
34688
|
-
import { basename as basename2, dirname, join as
|
|
34688
|
+
import { basename as basename2, dirname, join as join3, resolve } from "path";
|
|
34689
34689
|
init_zod();
|
|
34690
34690
|
|
|
34691
|
-
// src/claude-allow-rule.ts
|
|
34692
|
-
init_zod();
|
|
34693
|
-
init_file_settings();
|
|
34694
|
-
var ClaudeSettingsSchema = external_exports3.object({
|
|
34695
|
-
permissions: external_exports3.object({
|
|
34696
|
-
allow: external_exports3.array(external_exports3.string()).default([])
|
|
34697
|
-
}).loose().default(() => ({ allow: [] }))
|
|
34698
|
-
}).loose();
|
|
34699
|
-
|
|
34700
|
-
// src/channel-config.ts
|
|
34701
|
-
var McpConfigSchema = external_exports3.object({
|
|
34702
|
-
mcpServers: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional()
|
|
34703
|
-
}).loose();
|
|
34704
|
-
function slugifyDataDir(dataDir2) {
|
|
34705
|
-
const root = dataDir2.replace(/[\\/]\.hotsheet[\\/]?$/, "");
|
|
34706
|
-
const base = basename2(root) || "project";
|
|
34707
|
-
const slug = base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
34708
|
-
return slug !== "" ? slug : "project";
|
|
34709
|
-
}
|
|
34710
|
-
|
|
34711
34691
|
// src/channelLog.ts
|
|
34712
34692
|
import { appendFileSync, renameSync, statSync } from "fs";
|
|
34693
|
+
import { join as join2 } from "path";
|
|
34713
34694
|
var CHANNEL_LOG_MAX_BYTES = 1048576;
|
|
34714
|
-
function createChannelLogger(logPath) {
|
|
34695
|
+
function createChannelLogger(logPath, options = {}) {
|
|
34715
34696
|
let injectedBlankLine = false;
|
|
34697
|
+
const pidSuffix = options.pidLabel !== void 0 && options.pidLabel !== "" ? ` ${options.pidLabel}` : "";
|
|
34716
34698
|
return {
|
|
34717
34699
|
log(event, details) {
|
|
34718
34700
|
try {
|
|
@@ -34727,7 +34709,7 @@ function createChannelLogger(logPath) {
|
|
|
34727
34709
|
const detailsText = details === void 0 || details === "" ? "" : ` ${details}`;
|
|
34728
34710
|
const prefix = !injectedBlankLine && size > 0 ? "\n" : "";
|
|
34729
34711
|
injectedBlankLine = true;
|
|
34730
|
-
appendFileSync(logPath, `${prefix}[${ts}] [pid ${process.pid}] ${event}:${detailsText}
|
|
34712
|
+
appendFileSync(logPath, `${prefix}[${ts}] [pid ${process.pid}${pidSuffix}] ${event}:${detailsText}
|
|
34731
34713
|
`, "utf-8");
|
|
34732
34714
|
} catch {
|
|
34733
34715
|
}
|
|
@@ -34742,6 +34724,94 @@ function safeStatSize(path) {
|
|
|
34742
34724
|
}
|
|
34743
34725
|
}
|
|
34744
34726
|
|
|
34727
|
+
// src/channelPortFile.ts
|
|
34728
|
+
import { closeSync, openSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
34729
|
+
function readChannelInfo(portFile2) {
|
|
34730
|
+
let raw;
|
|
34731
|
+
try {
|
|
34732
|
+
raw = readFileSync2(portFile2, "utf-8").trim();
|
|
34733
|
+
} catch {
|
|
34734
|
+
return null;
|
|
34735
|
+
}
|
|
34736
|
+
if (raw === "") return null;
|
|
34737
|
+
if (raw.startsWith("{")) {
|
|
34738
|
+
try {
|
|
34739
|
+
const parsed = JSON.parse(raw);
|
|
34740
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
34741
|
+
const obj = parsed;
|
|
34742
|
+
if (typeof obj.port !== "number" || !Number.isInteger(obj.port)) return null;
|
|
34743
|
+
return {
|
|
34744
|
+
port: obj.port,
|
|
34745
|
+
pid: typeof obj.pid === "number" && Number.isInteger(obj.pid) ? obj.pid : null,
|
|
34746
|
+
slug: typeof obj.slug === "string" && obj.slug !== "" ? obj.slug : null,
|
|
34747
|
+
startedAt: typeof obj.startedAt === "string" && obj.startedAt !== "" ? obj.startedAt : null
|
|
34748
|
+
};
|
|
34749
|
+
} catch {
|
|
34750
|
+
return null;
|
|
34751
|
+
}
|
|
34752
|
+
}
|
|
34753
|
+
const legacyPort = parseInt(raw, 10);
|
|
34754
|
+
if (isNaN(legacyPort)) return null;
|
|
34755
|
+
return { port: legacyPort, pid: null, slug: null, startedAt: null };
|
|
34756
|
+
}
|
|
34757
|
+
function writeChannelInfo(portFile2, info) {
|
|
34758
|
+
const tmp = `${portFile2}.tmp.${process.pid.toString(36)}`;
|
|
34759
|
+
const body = JSON.stringify({
|
|
34760
|
+
port: info.port,
|
|
34761
|
+
pid: info.pid,
|
|
34762
|
+
slug: info.slug,
|
|
34763
|
+
startedAt: info.startedAt
|
|
34764
|
+
});
|
|
34765
|
+
writeFileSync(tmp, body, "utf-8");
|
|
34766
|
+
renameSync2(tmp, portFile2);
|
|
34767
|
+
try {
|
|
34768
|
+
const slash = portFile2.lastIndexOf("/");
|
|
34769
|
+
if (slash > 0) {
|
|
34770
|
+
const fd = openSync(portFile2.slice(0, slash), "r");
|
|
34771
|
+
try {
|
|
34772
|
+
closeSync(fd);
|
|
34773
|
+
} catch {
|
|
34774
|
+
}
|
|
34775
|
+
}
|
|
34776
|
+
} catch {
|
|
34777
|
+
}
|
|
34778
|
+
}
|
|
34779
|
+
function maybeUnlinkPortFile(portFile2, myPort2, myPid = process.pid) {
|
|
34780
|
+
const info = readChannelInfo(portFile2);
|
|
34781
|
+
if (info === null) return false;
|
|
34782
|
+
if (info.pid !== null) {
|
|
34783
|
+
if (info.pid !== myPid) return false;
|
|
34784
|
+
} else {
|
|
34785
|
+
if (info.port !== myPort2) return false;
|
|
34786
|
+
}
|
|
34787
|
+
try {
|
|
34788
|
+
unlinkSync(portFile2);
|
|
34789
|
+
return true;
|
|
34790
|
+
} catch {
|
|
34791
|
+
return false;
|
|
34792
|
+
}
|
|
34793
|
+
}
|
|
34794
|
+
|
|
34795
|
+
// src/claude-allow-rule.ts
|
|
34796
|
+
init_zod();
|
|
34797
|
+
init_file_settings();
|
|
34798
|
+
var ClaudeSettingsSchema = external_exports3.object({
|
|
34799
|
+
permissions: external_exports3.object({
|
|
34800
|
+
allow: external_exports3.array(external_exports3.string()).default([])
|
|
34801
|
+
}).loose().default(() => ({ allow: [] }))
|
|
34802
|
+
}).loose();
|
|
34803
|
+
|
|
34804
|
+
// src/channel-config.ts
|
|
34805
|
+
var McpConfigSchema = external_exports3.object({
|
|
34806
|
+
mcpServers: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional()
|
|
34807
|
+
}).loose();
|
|
34808
|
+
function slugifyDataDir(dataDir2) {
|
|
34809
|
+
const root = dataDir2.replace(/[\\/]\.hotsheet[\\/]?$/, "");
|
|
34810
|
+
const base = basename2(root) || "project";
|
|
34811
|
+
const slug = base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
34812
|
+
return slug !== "" ? slug : "project";
|
|
34813
|
+
}
|
|
34814
|
+
|
|
34745
34815
|
// src/channelPermissions.ts
|
|
34746
34816
|
var PERMISSION_TTL_MS = 12e4;
|
|
34747
34817
|
var queue = [];
|
|
@@ -34765,6 +34835,48 @@ function clearAllPermissions() {
|
|
|
34765
34835
|
queue.length = 0;
|
|
34766
34836
|
}
|
|
34767
34837
|
|
|
34838
|
+
// src/channelPortFileWatcher.ts
|
|
34839
|
+
var REWRITE_BURST_MAX = 5;
|
|
34840
|
+
var REWRITE_BURST_WINDOW_MS = 6e4;
|
|
34841
|
+
function installPortFileWatcher(opts) {
|
|
34842
|
+
const intervalMs = opts.intervalMs ?? 5e3;
|
|
34843
|
+
const setIntervalFn = opts.setIntervalFn ?? setInterval;
|
|
34844
|
+
const clearIntervalFn = opts.clearIntervalFn ?? ((handle2) => {
|
|
34845
|
+
clearInterval(handle2);
|
|
34846
|
+
});
|
|
34847
|
+
const rewriteTimestamps = [];
|
|
34848
|
+
let livelocked = false;
|
|
34849
|
+
const tick = () => {
|
|
34850
|
+
if (livelocked) return;
|
|
34851
|
+
const current = readChannelInfo(opts.portFile);
|
|
34852
|
+
if (current !== null && current.pid === opts.info.pid && current.port === opts.info.port && current.slug === opts.info.slug) {
|
|
34853
|
+
return;
|
|
34854
|
+
}
|
|
34855
|
+
const now = Date.now();
|
|
34856
|
+
while (rewriteTimestamps.length > 0 && now - rewriteTimestamps[0] > REWRITE_BURST_WINDOW_MS) {
|
|
34857
|
+
rewriteTimestamps.shift();
|
|
34858
|
+
}
|
|
34859
|
+
if (rewriteTimestamps.length >= REWRITE_BURST_MAX) {
|
|
34860
|
+
livelocked = true;
|
|
34861
|
+
opts.log?.("port-file-heal-livelock", `${String(REWRITE_BURST_MAX)} rewrites in ${String(REWRITE_BURST_WINDOW_MS)}ms \u2014 giving up to avoid hot-spin`);
|
|
34862
|
+
return;
|
|
34863
|
+
}
|
|
34864
|
+
rewriteTimestamps.push(now);
|
|
34865
|
+
const reason = current === null ? "vanished" : `pid-mismatch on-disk=${String(current.pid)} ours=${String(opts.info.pid)}`;
|
|
34866
|
+
try {
|
|
34867
|
+
writeChannelInfo(opts.portFile, opts.info);
|
|
34868
|
+
opts.log?.("port-file-heal-rewrite", reason);
|
|
34869
|
+
opts.notify?.();
|
|
34870
|
+
} catch (err) {
|
|
34871
|
+
opts.log?.("port-file-heal-error", String(err));
|
|
34872
|
+
}
|
|
34873
|
+
};
|
|
34874
|
+
const handle = setIntervalFn(tick, intervalMs);
|
|
34875
|
+
return () => {
|
|
34876
|
+
clearIntervalFn(handle);
|
|
34877
|
+
};
|
|
34878
|
+
}
|
|
34879
|
+
|
|
34768
34880
|
// src/channelStdioWatcher.ts
|
|
34769
34881
|
function installStdioDisconnectHandler(opts) {
|
|
34770
34882
|
const { stdin, stdout, onDisconnect, log } = opts;
|
|
@@ -34808,7 +34920,7 @@ function installStdioDisconnectHandler(opts) {
|
|
|
34808
34920
|
}
|
|
34809
34921
|
|
|
34810
34922
|
// src/channel.ts
|
|
34811
|
-
var CHANNEL_VERSION =
|
|
34923
|
+
var CHANNEL_VERSION = 8;
|
|
34812
34924
|
var dataDir = ".hotsheet";
|
|
34813
34925
|
var args = process.argv.slice(2);
|
|
34814
34926
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -34817,10 +34929,11 @@ for (let i = 0; i < args.length; i++) {
|
|
|
34817
34929
|
i++;
|
|
34818
34930
|
}
|
|
34819
34931
|
}
|
|
34820
|
-
var portFile =
|
|
34932
|
+
var portFile = join4(dataDir, "channel-port");
|
|
34821
34933
|
var serverSlug = slugifyDataDir(dataDir);
|
|
34822
34934
|
var serverName = `hotsheet-channel-${serverSlug}`;
|
|
34823
|
-
var
|
|
34935
|
+
var processStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34936
|
+
var channelLog = createChannelLogger(join4(dataDir, "mcp.log"));
|
|
34824
34937
|
channelLog.log("process-start", `argv=${process.argv.slice(2).join(" ")} dataDir=${dataDir} serverName=${serverName}`);
|
|
34825
34938
|
var mcp = new Server(
|
|
34826
34939
|
{ name: serverName, version: "0.1.0" },
|
|
@@ -34908,7 +35021,13 @@ var httpServer = createServer(async (req, res) => {
|
|
|
34908
35021
|
}
|
|
34909
35022
|
if (req.method === "GET" && req.url === "/health") {
|
|
34910
35023
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
34911
|
-
res.end(JSON.stringify({
|
|
35024
|
+
res.end(JSON.stringify({
|
|
35025
|
+
ok: true,
|
|
35026
|
+
version: CHANNEL_VERSION,
|
|
35027
|
+
pid: process.pid,
|
|
35028
|
+
slug: serverSlug,
|
|
35029
|
+
startedAt: processStartedAt
|
|
35030
|
+
}));
|
|
34912
35031
|
return;
|
|
34913
35032
|
}
|
|
34914
35033
|
if (req.method === "GET" && req.url === "/permission") {
|
|
@@ -35043,8 +35162,8 @@ var httpServer = createServer(async (req, res) => {
|
|
|
35043
35162
|
});
|
|
35044
35163
|
function notifyMainServer(abortSignal) {
|
|
35045
35164
|
try {
|
|
35046
|
-
const settingsPath =
|
|
35047
|
-
const settings = JSON.parse(
|
|
35165
|
+
const settingsPath = join4(dataDir, "settings.json");
|
|
35166
|
+
const settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
35048
35167
|
if (settings.port === void 0 || settings.port === 0) {
|
|
35049
35168
|
process.stderr.write(`[notify] no port in settings.json
|
|
35050
35169
|
`);
|
|
@@ -35068,14 +35187,32 @@ function notifyMainServer(abortSignal) {
|
|
|
35068
35187
|
return Promise.resolve();
|
|
35069
35188
|
}
|
|
35070
35189
|
}
|
|
35190
|
+
var myPort = null;
|
|
35191
|
+
var disposePortFileWatcher = null;
|
|
35071
35192
|
httpServer.listen(0, "127.0.0.1", () => {
|
|
35072
35193
|
const addr = httpServer.address();
|
|
35073
35194
|
if (addr !== null && typeof addr !== "string") {
|
|
35074
35195
|
const port = addr.port;
|
|
35196
|
+
myPort = port;
|
|
35197
|
+
const myInfo = {
|
|
35198
|
+
port,
|
|
35199
|
+
pid: process.pid,
|
|
35200
|
+
slug: serverSlug,
|
|
35201
|
+
startedAt: processStartedAt
|
|
35202
|
+
};
|
|
35075
35203
|
try {
|
|
35076
|
-
|
|
35204
|
+
writeChannelInfo(portFile, myInfo);
|
|
35077
35205
|
} catch {
|
|
35078
35206
|
}
|
|
35207
|
+
disposePortFileWatcher = installPortFileWatcher({
|
|
35208
|
+
portFile,
|
|
35209
|
+
info: myInfo,
|
|
35210
|
+
intervalMs: 5e3,
|
|
35211
|
+
log: (event, details) => channelLog.log(event, details),
|
|
35212
|
+
notify: () => {
|
|
35213
|
+
void notifyMainServer();
|
|
35214
|
+
}
|
|
35215
|
+
});
|
|
35079
35216
|
process.stderr.write(`${serverName} listening on port ${port}
|
|
35080
35217
|
`);
|
|
35081
35218
|
channelLog.log("http-listen", `port=${port}`);
|
|
@@ -35087,13 +35224,17 @@ var heartbeatInterval = setInterval(() => {
|
|
|
35087
35224
|
channelLog.log("heartbeat", `uptime=${process.uptime().toFixed(1)}s rss=${(mem.rss / 1024 / 1024).toFixed(1)}MiB`);
|
|
35088
35225
|
}, 6e4);
|
|
35089
35226
|
heartbeatInterval.unref();
|
|
35227
|
+
var cleanupInFlight = false;
|
|
35090
35228
|
async function cleanup(reason = "unspecified") {
|
|
35229
|
+
if (cleanupInFlight) return;
|
|
35230
|
+
cleanupInFlight = true;
|
|
35091
35231
|
channelLog.log("cleanup-start", `reason=${reason}`);
|
|
35092
35232
|
clearInterval(heartbeatInterval);
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35233
|
+
if (disposePortFileWatcher !== null) {
|
|
35234
|
+
disposePortFileWatcher();
|
|
35235
|
+
disposePortFileWatcher = null;
|
|
35096
35236
|
}
|
|
35237
|
+
if (myPort !== null) maybeUnlinkPortFile(portFile, myPort);
|
|
35097
35238
|
try {
|
|
35098
35239
|
const controller = new AbortController();
|
|
35099
35240
|
setTimeout(() => controller.abort(), 1e3);
|
|
@@ -35117,10 +35258,7 @@ process.on("SIGHUP", () => {
|
|
|
35117
35258
|
});
|
|
35118
35259
|
process.on("exit", (code) => {
|
|
35119
35260
|
channelLog.log("exit", `code=${code}`);
|
|
35120
|
-
|
|
35121
|
-
unlinkSync(portFile);
|
|
35122
|
-
} catch {
|
|
35123
|
-
}
|
|
35261
|
+
if (myPort !== null) maybeUnlinkPortFile(portFile, myPort);
|
|
35124
35262
|
});
|
|
35125
35263
|
export {
|
|
35126
35264
|
CHANNEL_VERSION
|