@clawos-dev/clawd 0.2.105 → 0.2.106-beta.202.2cc5279

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.cjs +98 -15
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -19257,8 +19257,8 @@ var require_websocket = __commonJS({
19257
19257
  "use strict";
19258
19258
  var EventEmitter2 = require("events");
19259
19259
  var https = require("https");
19260
- var http2 = require("http");
19261
- var net2 = require("net");
19260
+ var http3 = require("http");
19261
+ var net3 = require("net");
19262
19262
  var tls = require("tls");
19263
19263
  var { randomBytes, createHash: createHash2 } = require("crypto");
19264
19264
  var { Duplex, Readable: Readable3 } = require("stream");
@@ -19791,7 +19791,7 @@ var require_websocket = __commonJS({
19791
19791
  }
19792
19792
  const defaultPort = isSecure ? 443 : 80;
19793
19793
  const key = randomBytes(16).toString("base64");
19794
- const request = isSecure ? https.request : http2.request;
19794
+ const request = isSecure ? https.request : http3.request;
19795
19795
  const protocolSet = /* @__PURE__ */ new Set();
19796
19796
  let perMessageDeflate;
19797
19797
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -19992,12 +19992,12 @@ var require_websocket = __commonJS({
19992
19992
  }
19993
19993
  function netConnect(options) {
19994
19994
  options.path = options.socketPath;
19995
- return net2.connect(options);
19995
+ return net3.connect(options);
19996
19996
  }
19997
19997
  function tlsConnect(options) {
19998
19998
  options.path = void 0;
19999
19999
  if (!options.servername && options.servername !== "") {
20000
- options.servername = net2.isIP(options.host) ? "" : options.host;
20000
+ options.servername = net3.isIP(options.host) ? "" : options.host;
20001
20001
  }
20002
20002
  return tls.connect(options);
20003
20003
  }
@@ -20285,7 +20285,7 @@ var require_websocket_server = __commonJS({
20285
20285
  "../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
20286
20286
  "use strict";
20287
20287
  var EventEmitter2 = require("events");
20288
- var http2 = require("http");
20288
+ var http3 = require("http");
20289
20289
  var { Duplex } = require("stream");
20290
20290
  var { createHash: createHash2 } = require("crypto");
20291
20291
  var extension2 = require_extension();
@@ -20360,8 +20360,8 @@ var require_websocket_server = __commonJS({
20360
20360
  );
20361
20361
  }
20362
20362
  if (options.port != null) {
20363
- this._server = http2.createServer((req, res) => {
20364
- const body = http2.STATUS_CODES[426];
20363
+ this._server = http3.createServer((req, res) => {
20364
+ const body = http3.STATUS_CODES[426];
20365
20365
  res.writeHead(426, {
20366
20366
  "Content-Length": body.length,
20367
20367
  "Content-Type": "text/plain"
@@ -20648,7 +20648,7 @@ var require_websocket_server = __commonJS({
20648
20648
  this.destroy();
20649
20649
  }
20650
20650
  function abortHandshake(socket, code, message, headers) {
20651
- message = message || http2.STATUS_CODES[code];
20651
+ message = message || http3.STATUS_CODES[code];
20652
20652
  headers = {
20653
20653
  Connection: "close",
20654
20654
  "Content-Type": "text/html",
@@ -20657,7 +20657,7 @@ var require_websocket_server = __commonJS({
20657
20657
  };
20658
20658
  socket.once("finish", socket.destroy);
20659
20659
  socket.end(
20660
- `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
20660
+ `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
20661
20661
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
20662
20662
  );
20663
20663
  }
@@ -30870,6 +30870,8 @@ function resolveConfig(opts) {
30870
30870
  const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
30871
30871
  const rawMode = env.CLAWD_CC_MODE;
30872
30872
  const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
30873
+ const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
30874
+ const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
30873
30875
  return {
30874
30876
  port,
30875
30877
  host,
@@ -30881,7 +30883,8 @@ function resolveConfig(opts) {
30881
30883
  authToken,
30882
30884
  noTunnelPersist,
30883
30885
  frpcBinary,
30884
- mode
30886
+ mode,
30887
+ previewPorts
30885
30888
  };
30886
30889
  }
30887
30890
  var HELP_TEXT = `clawd [options]
@@ -36252,7 +36255,58 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
36252
36255
  var wrapper_default = import_websocket.default;
36253
36256
 
36254
36257
  // src/transport/local-ws-server.ts
36258
+ var import_node_http2 = __toESM(require("http"), 1);
36259
+
36260
+ // src/transport/preview-proxy.ts
36255
36261
  var import_node_http = __toESM(require("http"), 1);
36262
+ var import_node_net = __toESM(require("net"), 1);
36263
+ var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
36264
+ function matchPreviewPort(pathname) {
36265
+ const m2 = pathname.match(PREVIEW_RE);
36266
+ return m2 ? Number(m2[1]) : null;
36267
+ }
36268
+ function isPreviewPortAllowed(port, cfg) {
36269
+ return cfg.allowedPorts.includes(port);
36270
+ }
36271
+ function proxyPreviewHttp(req, res, port) {
36272
+ return new Promise((resolve6) => {
36273
+ const upstream = import_node_http.default.request(
36274
+ { host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
36275
+ (upRes) => {
36276
+ res.writeHead(upRes.statusCode || 502, upRes.headers);
36277
+ upRes.pipe(res);
36278
+ upRes.on("end", () => resolve6());
36279
+ }
36280
+ );
36281
+ upstream.on("error", (e) => {
36282
+ if (!res.headersSent) {
36283
+ res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
36284
+ }
36285
+ res.end(`preview upstream error: ${e.message}`);
36286
+ resolve6();
36287
+ });
36288
+ req.pipe(upstream);
36289
+ });
36290
+ }
36291
+ function proxyPreviewUpgrade(req, socket, head, port) {
36292
+ const upstream = import_node_net.default.connect(port, "localhost", () => {
36293
+ let raw = `${req.method} ${req.url} HTTP/1.1\r
36294
+ `;
36295
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
36296
+ raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
36297
+ `;
36298
+ }
36299
+ raw += "\r\n";
36300
+ upstream.write(raw);
36301
+ if (head && head.length) upstream.write(head);
36302
+ upstream.pipe(socket);
36303
+ socket.pipe(upstream);
36304
+ });
36305
+ upstream.on("error", () => socket.destroy());
36306
+ socket.on("error", () => upstream.destroy());
36307
+ }
36308
+
36309
+ // src/transport/local-ws-server.ts
36256
36310
  var LocalWsServer = class {
36257
36311
  constructor(opts) {
36258
36312
  this.opts = opts;
@@ -36272,9 +36326,25 @@ var LocalWsServer = class {
36272
36326
  async start() {
36273
36327
  const host = this.opts.host ?? "127.0.0.1";
36274
36328
  await new Promise((resolve6, reject) => {
36275
- const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
36329
+ const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
36276
36330
  const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
36277
36331
  httpServer.on("upgrade", (req, socket, head) => {
36332
+ if (req.url?.startsWith("/preview/")) {
36333
+ const pathname = (() => {
36334
+ try {
36335
+ return new URL(req.url, "http://localhost").pathname;
36336
+ } catch {
36337
+ return "/";
36338
+ }
36339
+ })();
36340
+ const port = matchPreviewPort(pathname);
36341
+ if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
36342
+ proxyPreviewUpgrade(req, socket, head, port);
36343
+ } else {
36344
+ socket.destroy();
36345
+ }
36346
+ return;
36347
+ }
36278
36348
  wss.handleUpgrade(req, socket, head, (ws) => {
36279
36349
  this.routeConnection(ws, req);
36280
36350
  });
@@ -37922,6 +37992,15 @@ function createHttpRouter(deps) {
37922
37992
  });
37923
37993
  return true;
37924
37994
  }
37995
+ if (url.pathname.startsWith("/preview/")) {
37996
+ const port = matchPreviewPort(url.pathname);
37997
+ if (port != null && isPreviewPortAllowed(port, { allowedPorts: deps.previewPorts ?? [] })) {
37998
+ await proxyPreviewHttp(req, res, port);
37999
+ return true;
38000
+ }
38001
+ sendJson(res, 403, { code: "PREVIEW_PORT_FORBIDDEN", message: "preview port not allowed" });
38002
+ return true;
38003
+ }
37925
38004
  if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
37926
38005
  return false;
37927
38006
  }
@@ -41518,7 +41597,7 @@ var import_node_path41 = __toESM(require("path"), 1);
41518
41597
  var import_promises10 = require("timers/promises");
41519
41598
 
41520
41599
  // src/extension/port-allocator.ts
41521
- var import_node_net = __toESM(require("net"), 1);
41600
+ var import_node_net2 = __toESM(require("net"), 1);
41522
41601
  var PortExhaustedError = class extends Error {
41523
41602
  constructor(min, max) {
41524
41603
  super(`no free port in [${min},${max}]`);
@@ -41531,7 +41610,7 @@ var PortExhaustedError = class extends Error {
41531
41610
  };
41532
41611
  function probe(port) {
41533
41612
  return new Promise((resolve6) => {
41534
- const srv = import_node_net.default.createServer();
41613
+ const srv = import_node_net2.default.createServer();
41535
41614
  srv.once("error", () => resolve6(false));
41536
41615
  srv.once("listening", () => {
41537
41616
  srv.close(() => resolve6(true));
@@ -42185,7 +42264,9 @@ async function startDaemon(config) {
42185
42264
  getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
42186
42265
  },
42187
42266
  // POST /extensions/import lands in ~/.clawd/extensions/<id>/
42188
- extensionsRoot: () => extensionsRoot()
42267
+ extensionsRoot: () => extensionsRoot(),
42268
+ // app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
42269
+ previewPorts: config.previewPorts ?? void 0
42189
42270
  });
42190
42271
  wsServer = new LocalWsServer({
42191
42272
  host: config.host,
@@ -42224,6 +42305,8 @@ async function startDaemon(config) {
42224
42305
  },
42225
42306
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
42226
42307
  httpRequestHandler: httpRouter,
42308
+ // app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
42309
+ previewPorts: config.previewPorts ?? void 0,
42227
42310
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
42228
42311
  // daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
42229
42312
  // 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.105",
3
+ "version": "0.2.106-beta.202.2cc5279",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",