hotsheet 0.17.0-beta.22 → 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 +227 -26
- package/dist/cli.js +257 -151
- package/dist/client/app.global.js +91 -91
- package/dist/client/styles.css +1 -1
- 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,9 +34685,113 @@ 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/channelLog.ts
|
|
34692
|
+
import { appendFileSync, renameSync, statSync } from "fs";
|
|
34693
|
+
import { join as join2 } from "path";
|
|
34694
|
+
var CHANNEL_LOG_MAX_BYTES = 1048576;
|
|
34695
|
+
function createChannelLogger(logPath, options = {}) {
|
|
34696
|
+
let injectedBlankLine = false;
|
|
34697
|
+
const pidSuffix = options.pidLabel !== void 0 && options.pidLabel !== "" ? ` ${options.pidLabel}` : "";
|
|
34698
|
+
return {
|
|
34699
|
+
log(event, details) {
|
|
34700
|
+
try {
|
|
34701
|
+
const size = safeStatSize(logPath);
|
|
34702
|
+
if (size >= CHANNEL_LOG_MAX_BYTES) {
|
|
34703
|
+
try {
|
|
34704
|
+
renameSync(logPath, `${logPath}.old`);
|
|
34705
|
+
} catch {
|
|
34706
|
+
}
|
|
34707
|
+
}
|
|
34708
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
34709
|
+
const detailsText = details === void 0 || details === "" ? "" : ` ${details}`;
|
|
34710
|
+
const prefix = !injectedBlankLine && size > 0 ? "\n" : "";
|
|
34711
|
+
injectedBlankLine = true;
|
|
34712
|
+
appendFileSync(logPath, `${prefix}[${ts}] [pid ${process.pid}${pidSuffix}] ${event}:${detailsText}
|
|
34713
|
+
`, "utf-8");
|
|
34714
|
+
} catch {
|
|
34715
|
+
}
|
|
34716
|
+
}
|
|
34717
|
+
};
|
|
34718
|
+
}
|
|
34719
|
+
function safeStatSize(path) {
|
|
34720
|
+
try {
|
|
34721
|
+
return statSync(path).size;
|
|
34722
|
+
} catch {
|
|
34723
|
+
return 0;
|
|
34724
|
+
}
|
|
34725
|
+
}
|
|
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
|
+
|
|
34691
34795
|
// src/claude-allow-rule.ts
|
|
34692
34796
|
init_zod();
|
|
34693
34797
|
init_file_settings();
|
|
@@ -34731,6 +34835,48 @@ function clearAllPermissions() {
|
|
|
34731
34835
|
queue.length = 0;
|
|
34732
34836
|
}
|
|
34733
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
|
+
|
|
34734
34880
|
// src/channelStdioWatcher.ts
|
|
34735
34881
|
function installStdioDisconnectHandler(opts) {
|
|
34736
34882
|
const { stdin, stdout, onDisconnect, log } = opts;
|
|
@@ -34774,7 +34920,7 @@ function installStdioDisconnectHandler(opts) {
|
|
|
34774
34920
|
}
|
|
34775
34921
|
|
|
34776
34922
|
// src/channel.ts
|
|
34777
|
-
var CHANNEL_VERSION =
|
|
34923
|
+
var CHANNEL_VERSION = 8;
|
|
34778
34924
|
var dataDir = ".hotsheet";
|
|
34779
34925
|
var args = process.argv.slice(2);
|
|
34780
34926
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -34783,9 +34929,12 @@ for (let i = 0; i < args.length; i++) {
|
|
|
34783
34929
|
i++;
|
|
34784
34930
|
}
|
|
34785
34931
|
}
|
|
34786
|
-
var portFile =
|
|
34932
|
+
var portFile = join4(dataDir, "channel-port");
|
|
34787
34933
|
var serverSlug = slugifyDataDir(dataDir);
|
|
34788
34934
|
var serverName = `hotsheet-channel-${serverSlug}`;
|
|
34935
|
+
var processStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34936
|
+
var channelLog = createChannelLogger(join4(dataDir, "mcp.log"));
|
|
34937
|
+
channelLog.log("process-start", `argv=${process.argv.slice(2).join(" ")} dataDir=${dataDir} serverName=${serverName}`);
|
|
34789
34938
|
var mcp = new Server(
|
|
34790
34939
|
{ name: serverName, version: "0.1.0" },
|
|
34791
34940
|
{
|
|
@@ -34816,17 +34965,23 @@ mcp.setRequestHandler(ListToolsRequestSchema, () => {
|
|
|
34816
34965
|
});
|
|
34817
34966
|
mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
34818
34967
|
const { name, arguments: args2 } = request.params;
|
|
34968
|
+
channelLog.log("tools/call", `name=${name}`);
|
|
34819
34969
|
const result = await callTool(name, args2 ?? {}, dataDir);
|
|
34820
34970
|
return result;
|
|
34821
34971
|
});
|
|
34822
34972
|
await mcp.connect(new StdioServerTransport());
|
|
34973
|
+
channelLog.log("mcp-connect", "StdioServerTransport ready");
|
|
34823
34974
|
installStdioDisconnectHandler({
|
|
34824
34975
|
stdin: process.stdin,
|
|
34825
34976
|
stdout: process.stdout,
|
|
34826
|
-
log: (msg) =>
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34977
|
+
log: (msg) => {
|
|
34978
|
+
process.stderr.write(`${serverName}: ${msg}
|
|
34979
|
+
`);
|
|
34980
|
+
channelLog.log("stdio-watcher", msg);
|
|
34981
|
+
},
|
|
34982
|
+
onDisconnect: (reason) => {
|
|
34983
|
+
channelLog.log("disconnect", `reason=${reason} \u2014 invoking cleanup`);
|
|
34984
|
+
void cleanup(`stdio-${reason}`);
|
|
34830
34985
|
}
|
|
34831
34986
|
});
|
|
34832
34987
|
var PermissionRequestSchema = external_exports3.object({
|
|
@@ -34866,7 +35021,13 @@ var httpServer = createServer(async (req, res) => {
|
|
|
34866
35021
|
}
|
|
34867
35022
|
if (req.method === "GET" && req.url === "/health") {
|
|
34868
35023
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
34869
|
-
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
|
+
}));
|
|
34870
35031
|
return;
|
|
34871
35032
|
}
|
|
34872
35033
|
if (req.method === "GET" && req.url === "/permission") {
|
|
@@ -34972,6 +35133,7 @@ var httpServer = createServer(async (req, res) => {
|
|
|
34972
35133
|
}
|
|
34973
35134
|
body += String(chunk);
|
|
34974
35135
|
}
|
|
35136
|
+
channelLog.log("trigger", `bodyBytes=${body.length}`);
|
|
34975
35137
|
try {
|
|
34976
35138
|
await mcp.notification({
|
|
34977
35139
|
method: "notifications/claude/channel",
|
|
@@ -34983,6 +35145,7 @@ var httpServer = createServer(async (req, res) => {
|
|
|
34983
35145
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
34984
35146
|
res.end(JSON.stringify({ ok: true }));
|
|
34985
35147
|
} catch (err) {
|
|
35148
|
+
channelLog.log("trigger-error", String(err));
|
|
34986
35149
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
34987
35150
|
res.end(JSON.stringify({ error: String(err) }));
|
|
34988
35151
|
}
|
|
@@ -34999,8 +35162,8 @@ var httpServer = createServer(async (req, res) => {
|
|
|
34999
35162
|
});
|
|
35000
35163
|
function notifyMainServer(abortSignal) {
|
|
35001
35164
|
try {
|
|
35002
|
-
const settingsPath =
|
|
35003
|
-
const settings = JSON.parse(
|
|
35165
|
+
const settingsPath = join4(dataDir, "settings.json");
|
|
35166
|
+
const settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
35004
35167
|
if (settings.port === void 0 || settings.port === 0) {
|
|
35005
35168
|
process.stderr.write(`[notify] no port in settings.json
|
|
35006
35169
|
`);
|
|
@@ -35024,40 +35187,78 @@ function notifyMainServer(abortSignal) {
|
|
|
35024
35187
|
return Promise.resolve();
|
|
35025
35188
|
}
|
|
35026
35189
|
}
|
|
35190
|
+
var myPort = null;
|
|
35191
|
+
var disposePortFileWatcher = null;
|
|
35027
35192
|
httpServer.listen(0, "127.0.0.1", () => {
|
|
35028
35193
|
const addr = httpServer.address();
|
|
35029
35194
|
if (addr !== null && typeof addr !== "string") {
|
|
35030
35195
|
const port = addr.port;
|
|
35196
|
+
myPort = port;
|
|
35197
|
+
const myInfo = {
|
|
35198
|
+
port,
|
|
35199
|
+
pid: process.pid,
|
|
35200
|
+
slug: serverSlug,
|
|
35201
|
+
startedAt: processStartedAt
|
|
35202
|
+
};
|
|
35031
35203
|
try {
|
|
35032
|
-
|
|
35204
|
+
writeChannelInfo(portFile, myInfo);
|
|
35033
35205
|
} catch {
|
|
35034
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
|
+
});
|
|
35035
35216
|
process.stderr.write(`${serverName} listening on port ${port}
|
|
35036
35217
|
`);
|
|
35218
|
+
channelLog.log("http-listen", `port=${port}`);
|
|
35037
35219
|
void notifyMainServer();
|
|
35038
35220
|
}
|
|
35039
35221
|
});
|
|
35040
|
-
|
|
35041
|
-
|
|
35042
|
-
|
|
35043
|
-
|
|
35044
|
-
|
|
35222
|
+
var heartbeatInterval = setInterval(() => {
|
|
35223
|
+
const mem = process.memoryUsage();
|
|
35224
|
+
channelLog.log("heartbeat", `uptime=${process.uptime().toFixed(1)}s rss=${(mem.rss / 1024 / 1024).toFixed(1)}MiB`);
|
|
35225
|
+
}, 6e4);
|
|
35226
|
+
heartbeatInterval.unref();
|
|
35227
|
+
var cleanupInFlight = false;
|
|
35228
|
+
async function cleanup(reason = "unspecified") {
|
|
35229
|
+
if (cleanupInFlight) return;
|
|
35230
|
+
cleanupInFlight = true;
|
|
35231
|
+
channelLog.log("cleanup-start", `reason=${reason}`);
|
|
35232
|
+
clearInterval(heartbeatInterval);
|
|
35233
|
+
if (disposePortFileWatcher !== null) {
|
|
35234
|
+
disposePortFileWatcher();
|
|
35235
|
+
disposePortFileWatcher = null;
|
|
35236
|
+
}
|
|
35237
|
+
if (myPort !== null) maybeUnlinkPortFile(portFile, myPort);
|
|
35045
35238
|
try {
|
|
35046
35239
|
const controller = new AbortController();
|
|
35047
35240
|
setTimeout(() => controller.abort(), 1e3);
|
|
35048
35241
|
await notifyMainServer(controller.signal);
|
|
35049
35242
|
} catch {
|
|
35050
35243
|
}
|
|
35244
|
+
channelLog.log("cleanup-end", `reason=${reason} \u2014 exiting`);
|
|
35051
35245
|
process.exit(0);
|
|
35052
35246
|
}
|
|
35053
|
-
process.on("SIGTERM", () =>
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
|
|
35247
|
+
process.on("SIGTERM", () => {
|
|
35248
|
+
channelLog.log("signal", "SIGTERM");
|
|
35249
|
+
void cleanup("SIGTERM");
|
|
35250
|
+
});
|
|
35251
|
+
process.on("SIGINT", () => {
|
|
35252
|
+
channelLog.log("signal", "SIGINT");
|
|
35253
|
+
void cleanup("SIGINT");
|
|
35254
|
+
});
|
|
35255
|
+
process.on("SIGHUP", () => {
|
|
35256
|
+
channelLog.log("signal", "SIGHUP");
|
|
35257
|
+
void cleanup("SIGHUP");
|
|
35258
|
+
});
|
|
35259
|
+
process.on("exit", (code) => {
|
|
35260
|
+
channelLog.log("exit", `code=${code}`);
|
|
35261
|
+
if (myPort !== null) maybeUnlinkPortFile(portFile, myPort);
|
|
35061
35262
|
});
|
|
35062
35263
|
export {
|
|
35063
35264
|
CHANNEL_VERSION
|