sessix-server 0.4.1 → 0.4.2

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/index.js CHANGED
@@ -1850,6 +1850,54 @@ var SessionManager = class {
1850
1850
  return stats ? { ...session, stats } : session;
1851
1851
  });
1852
1852
  }
1853
+ /**
1854
+ * 接入 ApprovalProxy 的非阻塞 hook 通知,将其映射为 ServerEvent 转发。
1855
+ *
1856
+ * 仅转发为 shared 类型定义中的字段,不把 hook 原始 payload 透传出去(隐私 + 体积)。
1857
+ * 当前覆盖:
1858
+ * - PreCompact (`type: 'compact'`) → `session_compact`
1859
+ * - PermissionDenied (`type: 'permission_denied'`) → `permission_denied`
1860
+ * - Subagent (`type: 'subagent'`) → `subagent_event`(骨架预留,本任务不发)
1861
+ */
1862
+ attachApprovalProxy(approvalProxy) {
1863
+ approvalProxy.onNotify((notification) => {
1864
+ const serverEvent = this.mapHookNotificationToServerEvent(notification);
1865
+ if (serverEvent) this.emit(serverEvent);
1866
+ });
1867
+ }
1868
+ /** 将 hook 通知映射为 ServerEvent;不识别的 type 返回 null */
1869
+ mapHookNotificationToServerEvent(n) {
1870
+ switch (n.type) {
1871
+ case "compact": {
1872
+ const subtype = n.subtype === "completed" || n.subtype === "blocked" ? n.subtype : "started";
1873
+ return { type: "session_compact", sessionId: n.sessionId, subtype };
1874
+ }
1875
+ case "permission_denied": {
1876
+ const source = n.source === "hook" || n.source === "rule" ? n.source : "classifier";
1877
+ return {
1878
+ type: "permission_denied",
1879
+ sessionId: n.sessionId,
1880
+ toolName: n.toolName ?? "unknown",
1881
+ reason: n.reason ?? "",
1882
+ source
1883
+ };
1884
+ }
1885
+ case "subagent": {
1886
+ const phase = n.subtype === "completed" ? "completed" : "started";
1887
+ return {
1888
+ type: "subagent_event",
1889
+ sessionId: n.sessionId,
1890
+ parentToolUseId: typeof n.parentToolUseId === "string" ? n.parentToolUseId : "",
1891
+ subAgentId: typeof n.subAgentId === "string" ? n.subAgentId : "",
1892
+ phase,
1893
+ task: typeof n.task === "string" ? n.task : void 0
1894
+ };
1895
+ }
1896
+ default:
1897
+ console.warn(`[SessionManager] Unknown hook notification type: ${n.type}`);
1898
+ return null;
1899
+ }
1900
+ }
1853
1901
  /**
1854
1902
  * 注册事件回调(事件会被转发到 WsBridge)
1855
1903
  *
@@ -2535,6 +2583,8 @@ var ApprovalProxy = class _ApprovalProxy {
2535
2583
  approvalRequestCallbacks = [];
2536
2584
  /** 审批 resolve 回调(任何来源的 resolve 都会触发,用于 WS 广播清理 */
2537
2585
  approvalResolvedCallbacks = [];
2586
+ /** Hook 通知回调(PreCompact / PermissionDenied / 等非阻塞 hook) */
2587
+ notifyCallbacks = [];
2538
2588
  /** YOLO 模式状态:sessionId -> enabled */
2539
2589
  yoloSessions = /* @__PURE__ */ new Map();
2540
2590
  /** 内存缓存:已被"始终允许"的工具名(避免每次读 settings.json) */
@@ -2596,6 +2646,24 @@ var ApprovalProxy = class _ApprovalProxy {
2596
2646
  }
2597
2647
  }
2598
2648
  }
2649
+ /**
2650
+ * 注册非阻塞 hook 通知回调(如 PreCompact、PermissionDenied)
2651
+ *
2652
+ * 这些 hook 不需要返回决策给 Claude Code,仅作为 ServerEvent 转发到手机端。
2653
+ */
2654
+ onNotify(callback) {
2655
+ this.notifyCallbacks.push(callback);
2656
+ }
2657
+ /** 触发所有 notify 回调(内部调用) */
2658
+ fireNotify(notification) {
2659
+ for (const callback of this.notifyCallbacks) {
2660
+ try {
2661
+ callback(notification);
2662
+ } catch (err) {
2663
+ console.error("[ApprovalProxy] Notify callback error:", err);
2664
+ }
2665
+ }
2666
+ }
2599
2667
  /** 设置状态信息提供者(用于 /health 端点) */
2600
2668
  setStatusInfoProvider(provider) {
2601
2669
  this.statusInfoProvider = provider;
@@ -2780,6 +2848,8 @@ var ApprovalProxy = class _ApprovalProxy {
2780
2848
  const pathname = url.pathname;
2781
2849
  if (req.method === "POST" && pathname === "/hook/approval") {
2782
2850
  this.handleApprovalHook(req, res);
2851
+ } else if (req.method === "POST" && pathname === "/hook/notify") {
2852
+ this.handleHookNotify(req, res);
2783
2853
  } else if (req.method === "POST" && pathname === "/pair") {
2784
2854
  this.handlePair(req, res);
2785
2855
  } else if (req.method === "GET" && pathname === "/health") {
@@ -2845,6 +2915,51 @@ var ApprovalProxy = class _ApprovalProxy {
2845
2915
  this.sendJson(res, 200, { decision: "deny", reason: "Server failed to process request" });
2846
2916
  }
2847
2917
  }
2918
+ /**
2919
+ * 非阻塞 hook 通知端点
2920
+ *
2921
+ * 用于 PreCompact、PostCompact、PermissionDenied 等 fire-and-forget 的 hook:
2922
+ * 立即返回 {"ok":true},再异步分发到 notifyCallbacks(最终广播为 ServerEvent)。
2923
+ *
2924
+ * 鉴权策略:仅允许 loopback(本机 hook 脚本)访问,避免远端注入伪造事件。
2925
+ */
2926
+ async handleHookNotify(req, res) {
2927
+ if (!this.isLoopbackRequest(req)) {
2928
+ this.sendJson(res, 403, { ok: false, reason: "forbidden" });
2929
+ return;
2930
+ }
2931
+ try {
2932
+ const body = await this.parseJsonBody(req);
2933
+ const sessionId = String(body.sessionId ?? "").trim();
2934
+ const type = String(body.type ?? "").trim();
2935
+ if (!sessionId || !type) {
2936
+ this.sendJson(res, 400, { error: "sessionId and type are required" });
2937
+ return;
2938
+ }
2939
+ const notification = {
2940
+ sessionId,
2941
+ type
2942
+ };
2943
+ if (typeof body.subtype === "string") notification.subtype = body.subtype;
2944
+ if (typeof body.toolName === "string") notification.toolName = body.toolName;
2945
+ if (typeof body.reason === "string") notification.reason = body.reason;
2946
+ if (typeof body.source === "string") notification.source = body.source;
2947
+ if (typeof body.parentToolUseId === "string") notification.parentToolUseId = body.parentToolUseId;
2948
+ if (typeof body.subAgentId === "string") notification.subAgentId = body.subAgentId;
2949
+ if (body.phase === "started" || body.phase === "completed") notification.phase = body.phase;
2950
+ if (typeof body.task === "string") notification.task = body.task;
2951
+ this.sendJson(res, 200, { ok: true });
2952
+ setImmediate(() => this.fireNotify(notification));
2953
+ } catch (err) {
2954
+ console.error("[ApprovalProxy] Hook notify failed:", err);
2955
+ this.sendJson(res, 200, { ok: false });
2956
+ }
2957
+ }
2958
+ /** 判断请求是否来自本机 loopback(127.0.0.1 / ::1 / IPv4-mapped IPv6) */
2959
+ isLoopbackRequest(req) {
2960
+ const remoteAddress = req.socket.remoteAddress;
2961
+ return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
2962
+ }
2848
2963
  /** 健康检查端点 */
2849
2964
  handleHealth(_req, res) {
2850
2965
  const info = this.statusInfoProvider?.() ?? { connections: 0, activeSessions: 0 };
@@ -2877,9 +2992,7 @@ var ApprovalProxy = class _ApprovalProxy {
2877
2992
  }
2878
2993
  /** 返回连接 token(仅本机访问) */
2879
2994
  handleToken(req, res) {
2880
- const remoteAddress = req.socket.remoteAddress;
2881
- const isLocal = remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
2882
- if (!isLocal) {
2995
+ if (!this.isLoopbackRequest(req)) {
2883
2996
  this.sendJson(res, 403, { error: t("approval.forbidden") });
2884
2997
  return;
2885
2998
  }
@@ -3107,9 +3220,15 @@ var import_node_os6 = require("os");
3107
3220
  var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
3108
3221
  var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
3109
3222
  var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
3223
+ var COMPACT_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "compact-hook.js");
3224
+ var POST_COMPACT_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "post-compact-hook.js");
3225
+ var PERMISSION_DENIED_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-denied-hook.js");
3110
3226
  var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
3111
3227
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
3112
3228
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
3229
+ var COMPACT_HOOK_COMMAND = "node ~/.sessix/hooks/compact-hook.js";
3230
+ var POST_COMPACT_HOOK_COMMAND = "node ~/.sessix/hooks/post-compact-hook.js";
3231
+ var PERMISSION_DENIED_HOOK_COMMAND = "node ~/.sessix/hooks/permission-denied-hook.js";
3113
3232
  var LEGACY_HOOK_COMMANDS = [
3114
3233
  "~/.sessix/hooks/approval-hook.sh",
3115
3234
  "~/.sessix/hooks/permission-accept.sh"
@@ -3169,6 +3288,112 @@ if (!process.env.SESSIX_SESSION_ID) process.exit(0)
3169
3288
  process.stdout.write('{"decision":"allow"}\\n')
3170
3289
  process.exit(0)
3171
3290
  `;
3291
+ var COMPACT_HOOK_TEMPLATE = `#!/usr/bin/env node
3292
+ // Sessix PreCompact \u901A\u77E5 hook
3293
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3294
+
3295
+ const sessionId = process.env.SESSIX_SESSION_ID
3296
+ if (!sessionId) {
3297
+ process.stdout.write('{}')
3298
+ process.exit(0)
3299
+ }
3300
+
3301
+ let raw = ''
3302
+ process.stdin.on('data', (chunk) => { raw += chunk })
3303
+ process.stdin.on('end', () => {
3304
+ // \u8BFB\u53D6\u5E76\u4E22\u5F03 stdin payload\uFF08PreCompact \u6807\u51C6\u8F93\u5165\uFF09\uFF0Cserver \u7AEF\u4E0D\u9700\u8981\u5B83
3305
+ try { JSON.parse(raw) } catch {}
3306
+ // fire-and-forget\uFF1A\u4E0D\u7B49\u5F85\u54CD\u5E94\uFF0C\u907F\u514D\u963B\u585E compact
3307
+ try {
3308
+ fetch('http://localhost:3746/hook/notify', {
3309
+ method: 'POST',
3310
+ headers: { 'Content-Type': 'application/json' },
3311
+ body: JSON.stringify({
3312
+ sessionId,
3313
+ type: 'compact',
3314
+ subtype: 'started',
3315
+ }),
3316
+ signal: AbortSignal.timeout(2000),
3317
+ }).catch(() => {})
3318
+ } catch {}
3319
+ // \u7ACB\u5373\u8FD4\u56DE\u7A7A JSON\uFF0C\u4E0D\u963B\u585E compact
3320
+ process.stdout.write('{}')
3321
+ process.exit(0)
3322
+ })
3323
+ `;
3324
+ var POST_COMPACT_HOOK_TEMPLATE = `#!/usr/bin/env node
3325
+ // Sessix PostCompact \u901A\u77E5 hook
3326
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3327
+
3328
+ const sessionId = process.env.SESSIX_SESSION_ID
3329
+ if (!sessionId) {
3330
+ process.stdout.write('{}')
3331
+ process.exit(0)
3332
+ }
3333
+
3334
+ let raw = ''
3335
+ process.stdin.on('data', (chunk) => { raw += chunk })
3336
+ process.stdin.on('end', () => {
3337
+ // \u8BFB\u53D6\u5E76\u4E22\u5F03 stdin payload\uFF0Cserver \u7AEF\u4E0D\u9700\u8981\u5B83
3338
+ try { JSON.parse(raw) } catch {}
3339
+ // fire-and-forget\uFF1A\u4E0D\u7B49\u5F85\u54CD\u5E94
3340
+ try {
3341
+ fetch('http://localhost:3746/hook/notify', {
3342
+ method: 'POST',
3343
+ headers: { 'Content-Type': 'application/json' },
3344
+ body: JSON.stringify({
3345
+ sessionId,
3346
+ type: 'compact',
3347
+ subtype: 'completed',
3348
+ }),
3349
+ signal: AbortSignal.timeout(2000),
3350
+ }).catch(() => {})
3351
+ } catch {}
3352
+ process.stdout.write('{}')
3353
+ process.exit(0)
3354
+ })
3355
+ `;
3356
+ var PERMISSION_DENIED_HOOK_TEMPLATE = `#!/usr/bin/env node
3357
+ // Sessix PermissionDenied \u901A\u77E5 hook
3358
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3359
+
3360
+ const sessionId = process.env.SESSIX_SESSION_ID
3361
+ if (!sessionId) {
3362
+ process.stdout.write('{}')
3363
+ process.exit(0)
3364
+ }
3365
+
3366
+ let raw = ''
3367
+ process.stdin.on('data', (chunk) => { raw += chunk })
3368
+ process.stdin.on('end', () => {
3369
+ let payload = {}
3370
+ try { payload = JSON.parse(raw) } catch {}
3371
+ const toolName = String(payload.tool_name || 'unknown')
3372
+ // Claude Code PermissionDenied \u7531 classifier \u89E6\u53D1\uFF0C\u6240\u4EE5 source \u9ED8\u8BA4 'classifier'
3373
+ const reason = String(
3374
+ payload.reason ||
3375
+ payload.permission_decision_reason ||
3376
+ payload.permissionDecisionReason ||
3377
+ ''
3378
+ )
3379
+ try {
3380
+ fetch('http://localhost:3746/hook/notify', {
3381
+ method: 'POST',
3382
+ headers: { 'Content-Type': 'application/json' },
3383
+ body: JSON.stringify({
3384
+ sessionId,
3385
+ type: 'permission_denied',
3386
+ toolName,
3387
+ reason,
3388
+ source: 'classifier',
3389
+ }),
3390
+ signal: AbortSignal.timeout(2000),
3391
+ }).catch(() => {})
3392
+ } catch {}
3393
+ process.stdout.write('{}')
3394
+ process.exit(0)
3395
+ })
3396
+ `;
3172
3397
  var HookInstaller = class {
3173
3398
  /**
3174
3399
  * 安装 hook
@@ -3182,8 +3407,14 @@ var HookInstaller = class {
3182
3407
  await (0, import_promises2.mkdir)(SESSIX_HOOKS_DIR, { recursive: true });
3183
3408
  await (0, import_promises2.writeFile)(HOOK_SCRIPT_PATH, HOOK_SCRIPT_TEMPLATE, "utf-8");
3184
3409
  await (0, import_promises2.writeFile)(PERMISSION_ACCEPT_PATH, PERMISSION_ACCEPT_TEMPLATE, "utf-8");
3410
+ await (0, import_promises2.writeFile)(COMPACT_HOOK_PATH, COMPACT_HOOK_TEMPLATE, "utf-8");
3411
+ await (0, import_promises2.writeFile)(POST_COMPACT_HOOK_PATH, POST_COMPACT_HOOK_TEMPLATE, "utf-8");
3412
+ await (0, import_promises2.writeFile)(PERMISSION_DENIED_HOOK_PATH, PERMISSION_DENIED_HOOK_TEMPLATE, "utf-8");
3185
3413
  await (0, import_promises2.chmod)(HOOK_SCRIPT_PATH, 493);
3186
3414
  await (0, import_promises2.chmod)(PERMISSION_ACCEPT_PATH, 493);
3415
+ await (0, import_promises2.chmod)(COMPACT_HOOK_PATH, 493);
3416
+ await (0, import_promises2.chmod)(POST_COMPACT_HOOK_PATH, 493);
3417
+ await (0, import_promises2.chmod)(PERMISSION_DENIED_HOOK_PATH, 493);
3187
3418
  await this.addHookToSettings();
3188
3419
  console.log("[HookInstaller] Hook installation complete");
3189
3420
  }
@@ -3209,6 +3440,9 @@ var HookInstaller = class {
3209
3440
  async isInstalled() {
3210
3441
  let approvalScriptContent = "";
3211
3442
  let permissionScriptExists = false;
3443
+ let compactScriptExists = false;
3444
+ let postCompactScriptExists = false;
3445
+ let permissionDeniedScriptExists = false;
3212
3446
  try {
3213
3447
  approvalScriptContent = await (0, import_promises2.readFile)(HOOK_SCRIPT_PATH, "utf-8");
3214
3448
  } catch {
@@ -3218,10 +3452,25 @@ var HookInstaller = class {
3218
3452
  permissionScriptExists = true;
3219
3453
  } catch {
3220
3454
  }
3455
+ try {
3456
+ await (0, import_promises2.access)(COMPACT_HOOK_PATH);
3457
+ compactScriptExists = true;
3458
+ } catch {
3459
+ }
3460
+ try {
3461
+ await (0, import_promises2.access)(POST_COMPACT_HOOK_PATH);
3462
+ postCompactScriptExists = true;
3463
+ } catch {
3464
+ }
3465
+ try {
3466
+ await (0, import_promises2.access)(PERMISSION_DENIED_HOOK_PATH);
3467
+ permissionDeniedScriptExists = true;
3468
+ } catch {
3469
+ }
3221
3470
  const isLatestVersion = approvalScriptContent.includes("permissionDecision");
3222
3471
  const settings = await this.readClaudeSettings();
3223
3472
  const configExists = this.hasHookConfig(settings);
3224
- return isLatestVersion && permissionScriptExists && configExists;
3473
+ return isLatestVersion && permissionScriptExists && compactScriptExists && postCompactScriptExists && permissionDeniedScriptExists && configExists;
3225
3474
  }
3226
3475
  // ============================================
3227
3476
  // 内部方法
@@ -3259,6 +3508,36 @@ var HookInstaller = class {
3259
3508
  });
3260
3509
  changed = true;
3261
3510
  }
3511
+ if (!this.hasPreCompactConfig(settings)) {
3512
+ if (!settings.hooks.PreCompact) {
3513
+ settings.hooks.PreCompact = [];
3514
+ }
3515
+ settings.hooks.PreCompact.push({
3516
+ matcher: "",
3517
+ hooks: [{ type: "command", command: COMPACT_HOOK_COMMAND }]
3518
+ });
3519
+ changed = true;
3520
+ }
3521
+ if (!this.hasPostCompactConfig(settings)) {
3522
+ if (!settings.hooks.PostCompact) {
3523
+ settings.hooks.PostCompact = [];
3524
+ }
3525
+ settings.hooks.PostCompact.push({
3526
+ matcher: "",
3527
+ hooks: [{ type: "command", command: POST_COMPACT_HOOK_COMMAND }]
3528
+ });
3529
+ changed = true;
3530
+ }
3531
+ if (!this.hasPermissionDeniedConfig(settings)) {
3532
+ if (!settings.hooks.PermissionDenied) {
3533
+ settings.hooks.PermissionDenied = [];
3534
+ }
3535
+ settings.hooks.PermissionDenied.push({
3536
+ matcher: "",
3537
+ hooks: [{ type: "command", command: PERMISSION_DENIED_HOOK_COMMAND }]
3538
+ });
3539
+ changed = true;
3540
+ }
3262
3541
  if (changed) {
3263
3542
  await this.writeClaudeSettings(settings);
3264
3543
  } else {
@@ -3273,6 +3552,9 @@ var HookInstaller = class {
3273
3552
  if (!settings.hooks) return;
3274
3553
  this.removeHookCommand(settings, "PreToolUse", HOOK_COMMAND);
3275
3554
  this.removeHookCommand(settings, "PermissionRequest", PERMISSION_ACCEPT_COMMAND);
3555
+ this.removeHookCommand(settings, "PreCompact", COMPACT_HOOK_COMMAND);
3556
+ this.removeHookCommand(settings, "PostCompact", POST_COMPACT_HOOK_COMMAND);
3557
+ this.removeHookCommand(settings, "PermissionDenied", PERMISSION_DENIED_HOOK_COMMAND);
3276
3558
  if (Object.keys(settings.hooks).length === 0) {
3277
3559
  delete settings.hooks;
3278
3560
  }
@@ -3310,7 +3592,7 @@ var HookInstaller = class {
3310
3592
  * 检查 settings 中是否已包含所有 Sessix hook 配置
3311
3593
  */
3312
3594
  hasHookConfig(settings) {
3313
- return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
3595
+ return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings) && this.hasPreCompactConfig(settings) && this.hasPostCompactConfig(settings) && this.hasPermissionDeniedConfig(settings);
3314
3596
  }
3315
3597
  /** 检查 PreToolUse 中是否有 approval-hook.js */
3316
3598
  hasPreToolUseConfig(settings) {
@@ -3320,6 +3602,18 @@ var HookInstaller = class {
3320
3602
  hasPermissionRequestConfig(settings) {
3321
3603
  return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
3322
3604
  }
3605
+ /** 检查 PreCompact 中是否有 compact-hook.js */
3606
+ hasPreCompactConfig(settings) {
3607
+ return this.hasHookEntry(settings?.hooks?.PreCompact, COMPACT_HOOK_COMMAND);
3608
+ }
3609
+ /** 检查 PostCompact 中是否有 post-compact-hook.js */
3610
+ hasPostCompactConfig(settings) {
3611
+ return this.hasHookEntry(settings?.hooks?.PostCompact, POST_COMPACT_HOOK_COMMAND);
3612
+ }
3613
+ /** 检查 PermissionDenied 中是否有 permission-denied-hook.js */
3614
+ hasPermissionDeniedConfig(settings) {
3615
+ return this.hasHookEntry(settings?.hooks?.PermissionDenied, PERMISSION_DENIED_HOOK_COMMAND);
3616
+ }
3323
3617
  /** 检查 hook 数组中是否包含指定命令 */
3324
3618
  hasHookEntry(hookArray, command) {
3325
3619
  if (!Array.isArray(hookArray)) return false;
@@ -5049,6 +5343,7 @@ async function start(opts = {}) {
5049
5343
  onStateChange: (state) => mdnsService?.updatePairingState(state)
5050
5344
  });
5051
5345
  approvalProxy.setPairingManager(pairingManager);
5346
+ sessionManager.attachApprovalProxy(approvalProxy);
5052
5347
  const authManager = new AuthManager();
5053
5348
  authManager.on("login_url", (url) => {
5054
5349
  wsBridge.broadcast({ type: "auth_login_url", url });
package/dist/server.js CHANGED
@@ -1855,6 +1855,54 @@ var SessionManager = class {
1855
1855
  return stats ? { ...session, stats } : session;
1856
1856
  });
1857
1857
  }
1858
+ /**
1859
+ * 接入 ApprovalProxy 的非阻塞 hook 通知,将其映射为 ServerEvent 转发。
1860
+ *
1861
+ * 仅转发为 shared 类型定义中的字段,不把 hook 原始 payload 透传出去(隐私 + 体积)。
1862
+ * 当前覆盖:
1863
+ * - PreCompact (`type: 'compact'`) → `session_compact`
1864
+ * - PermissionDenied (`type: 'permission_denied'`) → `permission_denied`
1865
+ * - Subagent (`type: 'subagent'`) → `subagent_event`(骨架预留,本任务不发)
1866
+ */
1867
+ attachApprovalProxy(approvalProxy) {
1868
+ approvalProxy.onNotify((notification) => {
1869
+ const serverEvent = this.mapHookNotificationToServerEvent(notification);
1870
+ if (serverEvent) this.emit(serverEvent);
1871
+ });
1872
+ }
1873
+ /** 将 hook 通知映射为 ServerEvent;不识别的 type 返回 null */
1874
+ mapHookNotificationToServerEvent(n) {
1875
+ switch (n.type) {
1876
+ case "compact": {
1877
+ const subtype = n.subtype === "completed" || n.subtype === "blocked" ? n.subtype : "started";
1878
+ return { type: "session_compact", sessionId: n.sessionId, subtype };
1879
+ }
1880
+ case "permission_denied": {
1881
+ const source = n.source === "hook" || n.source === "rule" ? n.source : "classifier";
1882
+ return {
1883
+ type: "permission_denied",
1884
+ sessionId: n.sessionId,
1885
+ toolName: n.toolName ?? "unknown",
1886
+ reason: n.reason ?? "",
1887
+ source
1888
+ };
1889
+ }
1890
+ case "subagent": {
1891
+ const phase = n.subtype === "completed" ? "completed" : "started";
1892
+ return {
1893
+ type: "subagent_event",
1894
+ sessionId: n.sessionId,
1895
+ parentToolUseId: typeof n.parentToolUseId === "string" ? n.parentToolUseId : "",
1896
+ subAgentId: typeof n.subAgentId === "string" ? n.subAgentId : "",
1897
+ phase,
1898
+ task: typeof n.task === "string" ? n.task : void 0
1899
+ };
1900
+ }
1901
+ default:
1902
+ console.warn(`[SessionManager] Unknown hook notification type: ${n.type}`);
1903
+ return null;
1904
+ }
1905
+ }
1858
1906
  /**
1859
1907
  * 注册事件回调(事件会被转发到 WsBridge)
1860
1908
  *
@@ -2540,6 +2588,8 @@ var ApprovalProxy = class _ApprovalProxy {
2540
2588
  approvalRequestCallbacks = [];
2541
2589
  /** 审批 resolve 回调(任何来源的 resolve 都会触发,用于 WS 广播清理 */
2542
2590
  approvalResolvedCallbacks = [];
2591
+ /** Hook 通知回调(PreCompact / PermissionDenied / 等非阻塞 hook) */
2592
+ notifyCallbacks = [];
2543
2593
  /** YOLO 模式状态:sessionId -> enabled */
2544
2594
  yoloSessions = /* @__PURE__ */ new Map();
2545
2595
  /** 内存缓存:已被"始终允许"的工具名(避免每次读 settings.json) */
@@ -2601,6 +2651,24 @@ var ApprovalProxy = class _ApprovalProxy {
2601
2651
  }
2602
2652
  }
2603
2653
  }
2654
+ /**
2655
+ * 注册非阻塞 hook 通知回调(如 PreCompact、PermissionDenied)
2656
+ *
2657
+ * 这些 hook 不需要返回决策给 Claude Code,仅作为 ServerEvent 转发到手机端。
2658
+ */
2659
+ onNotify(callback) {
2660
+ this.notifyCallbacks.push(callback);
2661
+ }
2662
+ /** 触发所有 notify 回调(内部调用) */
2663
+ fireNotify(notification) {
2664
+ for (const callback of this.notifyCallbacks) {
2665
+ try {
2666
+ callback(notification);
2667
+ } catch (err) {
2668
+ console.error("[ApprovalProxy] Notify callback error:", err);
2669
+ }
2670
+ }
2671
+ }
2604
2672
  /** 设置状态信息提供者(用于 /health 端点) */
2605
2673
  setStatusInfoProvider(provider) {
2606
2674
  this.statusInfoProvider = provider;
@@ -2785,6 +2853,8 @@ var ApprovalProxy = class _ApprovalProxy {
2785
2853
  const pathname = url.pathname;
2786
2854
  if (req.method === "POST" && pathname === "/hook/approval") {
2787
2855
  this.handleApprovalHook(req, res);
2856
+ } else if (req.method === "POST" && pathname === "/hook/notify") {
2857
+ this.handleHookNotify(req, res);
2788
2858
  } else if (req.method === "POST" && pathname === "/pair") {
2789
2859
  this.handlePair(req, res);
2790
2860
  } else if (req.method === "GET" && pathname === "/health") {
@@ -2850,6 +2920,51 @@ var ApprovalProxy = class _ApprovalProxy {
2850
2920
  this.sendJson(res, 200, { decision: "deny", reason: "Server failed to process request" });
2851
2921
  }
2852
2922
  }
2923
+ /**
2924
+ * 非阻塞 hook 通知端点
2925
+ *
2926
+ * 用于 PreCompact、PostCompact、PermissionDenied 等 fire-and-forget 的 hook:
2927
+ * 立即返回 {"ok":true},再异步分发到 notifyCallbacks(最终广播为 ServerEvent)。
2928
+ *
2929
+ * 鉴权策略:仅允许 loopback(本机 hook 脚本)访问,避免远端注入伪造事件。
2930
+ */
2931
+ async handleHookNotify(req, res) {
2932
+ if (!this.isLoopbackRequest(req)) {
2933
+ this.sendJson(res, 403, { ok: false, reason: "forbidden" });
2934
+ return;
2935
+ }
2936
+ try {
2937
+ const body = await this.parseJsonBody(req);
2938
+ const sessionId = String(body.sessionId ?? "").trim();
2939
+ const type = String(body.type ?? "").trim();
2940
+ if (!sessionId || !type) {
2941
+ this.sendJson(res, 400, { error: "sessionId and type are required" });
2942
+ return;
2943
+ }
2944
+ const notification = {
2945
+ sessionId,
2946
+ type
2947
+ };
2948
+ if (typeof body.subtype === "string") notification.subtype = body.subtype;
2949
+ if (typeof body.toolName === "string") notification.toolName = body.toolName;
2950
+ if (typeof body.reason === "string") notification.reason = body.reason;
2951
+ if (typeof body.source === "string") notification.source = body.source;
2952
+ if (typeof body.parentToolUseId === "string") notification.parentToolUseId = body.parentToolUseId;
2953
+ if (typeof body.subAgentId === "string") notification.subAgentId = body.subAgentId;
2954
+ if (body.phase === "started" || body.phase === "completed") notification.phase = body.phase;
2955
+ if (typeof body.task === "string") notification.task = body.task;
2956
+ this.sendJson(res, 200, { ok: true });
2957
+ setImmediate(() => this.fireNotify(notification));
2958
+ } catch (err) {
2959
+ console.error("[ApprovalProxy] Hook notify failed:", err);
2960
+ this.sendJson(res, 200, { ok: false });
2961
+ }
2962
+ }
2963
+ /** 判断请求是否来自本机 loopback(127.0.0.1 / ::1 / IPv4-mapped IPv6) */
2964
+ isLoopbackRequest(req) {
2965
+ const remoteAddress = req.socket.remoteAddress;
2966
+ return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
2967
+ }
2853
2968
  /** 健康检查端点 */
2854
2969
  handleHealth(_req, res) {
2855
2970
  const info = this.statusInfoProvider?.() ?? { connections: 0, activeSessions: 0 };
@@ -2882,9 +2997,7 @@ var ApprovalProxy = class _ApprovalProxy {
2882
2997
  }
2883
2998
  /** 返回连接 token(仅本机访问) */
2884
2999
  handleToken(req, res) {
2885
- const remoteAddress = req.socket.remoteAddress;
2886
- const isLocal = remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
2887
- if (!isLocal) {
3000
+ if (!this.isLoopbackRequest(req)) {
2888
3001
  this.sendJson(res, 403, { error: t("approval.forbidden") });
2889
3002
  return;
2890
3003
  }
@@ -3112,9 +3225,15 @@ var import_node_os6 = require("os");
3112
3225
  var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
3113
3226
  var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
3114
3227
  var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
3228
+ var COMPACT_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "compact-hook.js");
3229
+ var POST_COMPACT_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "post-compact-hook.js");
3230
+ var PERMISSION_DENIED_HOOK_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-denied-hook.js");
3115
3231
  var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
3116
3232
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
3117
3233
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
3234
+ var COMPACT_HOOK_COMMAND = "node ~/.sessix/hooks/compact-hook.js";
3235
+ var POST_COMPACT_HOOK_COMMAND = "node ~/.sessix/hooks/post-compact-hook.js";
3236
+ var PERMISSION_DENIED_HOOK_COMMAND = "node ~/.sessix/hooks/permission-denied-hook.js";
3118
3237
  var LEGACY_HOOK_COMMANDS = [
3119
3238
  "~/.sessix/hooks/approval-hook.sh",
3120
3239
  "~/.sessix/hooks/permission-accept.sh"
@@ -3174,6 +3293,112 @@ if (!process.env.SESSIX_SESSION_ID) process.exit(0)
3174
3293
  process.stdout.write('{"decision":"allow"}\\n')
3175
3294
  process.exit(0)
3176
3295
  `;
3296
+ var COMPACT_HOOK_TEMPLATE = `#!/usr/bin/env node
3297
+ // Sessix PreCompact \u901A\u77E5 hook
3298
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3299
+
3300
+ const sessionId = process.env.SESSIX_SESSION_ID
3301
+ if (!sessionId) {
3302
+ process.stdout.write('{}')
3303
+ process.exit(0)
3304
+ }
3305
+
3306
+ let raw = ''
3307
+ process.stdin.on('data', (chunk) => { raw += chunk })
3308
+ process.stdin.on('end', () => {
3309
+ // \u8BFB\u53D6\u5E76\u4E22\u5F03 stdin payload\uFF08PreCompact \u6807\u51C6\u8F93\u5165\uFF09\uFF0Cserver \u7AEF\u4E0D\u9700\u8981\u5B83
3310
+ try { JSON.parse(raw) } catch {}
3311
+ // fire-and-forget\uFF1A\u4E0D\u7B49\u5F85\u54CD\u5E94\uFF0C\u907F\u514D\u963B\u585E compact
3312
+ try {
3313
+ fetch('http://localhost:3746/hook/notify', {
3314
+ method: 'POST',
3315
+ headers: { 'Content-Type': 'application/json' },
3316
+ body: JSON.stringify({
3317
+ sessionId,
3318
+ type: 'compact',
3319
+ subtype: 'started',
3320
+ }),
3321
+ signal: AbortSignal.timeout(2000),
3322
+ }).catch(() => {})
3323
+ } catch {}
3324
+ // \u7ACB\u5373\u8FD4\u56DE\u7A7A JSON\uFF0C\u4E0D\u963B\u585E compact
3325
+ process.stdout.write('{}')
3326
+ process.exit(0)
3327
+ })
3328
+ `;
3329
+ var POST_COMPACT_HOOK_TEMPLATE = `#!/usr/bin/env node
3330
+ // Sessix PostCompact \u901A\u77E5 hook
3331
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3332
+
3333
+ const sessionId = process.env.SESSIX_SESSION_ID
3334
+ if (!sessionId) {
3335
+ process.stdout.write('{}')
3336
+ process.exit(0)
3337
+ }
3338
+
3339
+ let raw = ''
3340
+ process.stdin.on('data', (chunk) => { raw += chunk })
3341
+ process.stdin.on('end', () => {
3342
+ // \u8BFB\u53D6\u5E76\u4E22\u5F03 stdin payload\uFF0Cserver \u7AEF\u4E0D\u9700\u8981\u5B83
3343
+ try { JSON.parse(raw) } catch {}
3344
+ // fire-and-forget\uFF1A\u4E0D\u7B49\u5F85\u54CD\u5E94
3345
+ try {
3346
+ fetch('http://localhost:3746/hook/notify', {
3347
+ method: 'POST',
3348
+ headers: { 'Content-Type': 'application/json' },
3349
+ body: JSON.stringify({
3350
+ sessionId,
3351
+ type: 'compact',
3352
+ subtype: 'completed',
3353
+ }),
3354
+ signal: AbortSignal.timeout(2000),
3355
+ }).catch(() => {})
3356
+ } catch {}
3357
+ process.stdout.write('{}')
3358
+ process.exit(0)
3359
+ })
3360
+ `;
3361
+ var PERMISSION_DENIED_HOOK_TEMPLATE = `#!/usr/bin/env node
3362
+ // Sessix PermissionDenied \u901A\u77E5 hook
3363
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
3364
+
3365
+ const sessionId = process.env.SESSIX_SESSION_ID
3366
+ if (!sessionId) {
3367
+ process.stdout.write('{}')
3368
+ process.exit(0)
3369
+ }
3370
+
3371
+ let raw = ''
3372
+ process.stdin.on('data', (chunk) => { raw += chunk })
3373
+ process.stdin.on('end', () => {
3374
+ let payload = {}
3375
+ try { payload = JSON.parse(raw) } catch {}
3376
+ const toolName = String(payload.tool_name || 'unknown')
3377
+ // Claude Code PermissionDenied \u7531 classifier \u89E6\u53D1\uFF0C\u6240\u4EE5 source \u9ED8\u8BA4 'classifier'
3378
+ const reason = String(
3379
+ payload.reason ||
3380
+ payload.permission_decision_reason ||
3381
+ payload.permissionDecisionReason ||
3382
+ ''
3383
+ )
3384
+ try {
3385
+ fetch('http://localhost:3746/hook/notify', {
3386
+ method: 'POST',
3387
+ headers: { 'Content-Type': 'application/json' },
3388
+ body: JSON.stringify({
3389
+ sessionId,
3390
+ type: 'permission_denied',
3391
+ toolName,
3392
+ reason,
3393
+ source: 'classifier',
3394
+ }),
3395
+ signal: AbortSignal.timeout(2000),
3396
+ }).catch(() => {})
3397
+ } catch {}
3398
+ process.stdout.write('{}')
3399
+ process.exit(0)
3400
+ })
3401
+ `;
3177
3402
  var HookInstaller = class {
3178
3403
  /**
3179
3404
  * 安装 hook
@@ -3187,8 +3412,14 @@ var HookInstaller = class {
3187
3412
  await (0, import_promises2.mkdir)(SESSIX_HOOKS_DIR, { recursive: true });
3188
3413
  await (0, import_promises2.writeFile)(HOOK_SCRIPT_PATH, HOOK_SCRIPT_TEMPLATE, "utf-8");
3189
3414
  await (0, import_promises2.writeFile)(PERMISSION_ACCEPT_PATH, PERMISSION_ACCEPT_TEMPLATE, "utf-8");
3415
+ await (0, import_promises2.writeFile)(COMPACT_HOOK_PATH, COMPACT_HOOK_TEMPLATE, "utf-8");
3416
+ await (0, import_promises2.writeFile)(POST_COMPACT_HOOK_PATH, POST_COMPACT_HOOK_TEMPLATE, "utf-8");
3417
+ await (0, import_promises2.writeFile)(PERMISSION_DENIED_HOOK_PATH, PERMISSION_DENIED_HOOK_TEMPLATE, "utf-8");
3190
3418
  await (0, import_promises2.chmod)(HOOK_SCRIPT_PATH, 493);
3191
3419
  await (0, import_promises2.chmod)(PERMISSION_ACCEPT_PATH, 493);
3420
+ await (0, import_promises2.chmod)(COMPACT_HOOK_PATH, 493);
3421
+ await (0, import_promises2.chmod)(POST_COMPACT_HOOK_PATH, 493);
3422
+ await (0, import_promises2.chmod)(PERMISSION_DENIED_HOOK_PATH, 493);
3192
3423
  await this.addHookToSettings();
3193
3424
  console.log("[HookInstaller] Hook installation complete");
3194
3425
  }
@@ -3214,6 +3445,9 @@ var HookInstaller = class {
3214
3445
  async isInstalled() {
3215
3446
  let approvalScriptContent = "";
3216
3447
  let permissionScriptExists = false;
3448
+ let compactScriptExists = false;
3449
+ let postCompactScriptExists = false;
3450
+ let permissionDeniedScriptExists = false;
3217
3451
  try {
3218
3452
  approvalScriptContent = await (0, import_promises2.readFile)(HOOK_SCRIPT_PATH, "utf-8");
3219
3453
  } catch {
@@ -3223,10 +3457,25 @@ var HookInstaller = class {
3223
3457
  permissionScriptExists = true;
3224
3458
  } catch {
3225
3459
  }
3460
+ try {
3461
+ await (0, import_promises2.access)(COMPACT_HOOK_PATH);
3462
+ compactScriptExists = true;
3463
+ } catch {
3464
+ }
3465
+ try {
3466
+ await (0, import_promises2.access)(POST_COMPACT_HOOK_PATH);
3467
+ postCompactScriptExists = true;
3468
+ } catch {
3469
+ }
3470
+ try {
3471
+ await (0, import_promises2.access)(PERMISSION_DENIED_HOOK_PATH);
3472
+ permissionDeniedScriptExists = true;
3473
+ } catch {
3474
+ }
3226
3475
  const isLatestVersion = approvalScriptContent.includes("permissionDecision");
3227
3476
  const settings = await this.readClaudeSettings();
3228
3477
  const configExists = this.hasHookConfig(settings);
3229
- return isLatestVersion && permissionScriptExists && configExists;
3478
+ return isLatestVersion && permissionScriptExists && compactScriptExists && postCompactScriptExists && permissionDeniedScriptExists && configExists;
3230
3479
  }
3231
3480
  // ============================================
3232
3481
  // 内部方法
@@ -3264,6 +3513,36 @@ var HookInstaller = class {
3264
3513
  });
3265
3514
  changed = true;
3266
3515
  }
3516
+ if (!this.hasPreCompactConfig(settings)) {
3517
+ if (!settings.hooks.PreCompact) {
3518
+ settings.hooks.PreCompact = [];
3519
+ }
3520
+ settings.hooks.PreCompact.push({
3521
+ matcher: "",
3522
+ hooks: [{ type: "command", command: COMPACT_HOOK_COMMAND }]
3523
+ });
3524
+ changed = true;
3525
+ }
3526
+ if (!this.hasPostCompactConfig(settings)) {
3527
+ if (!settings.hooks.PostCompact) {
3528
+ settings.hooks.PostCompact = [];
3529
+ }
3530
+ settings.hooks.PostCompact.push({
3531
+ matcher: "",
3532
+ hooks: [{ type: "command", command: POST_COMPACT_HOOK_COMMAND }]
3533
+ });
3534
+ changed = true;
3535
+ }
3536
+ if (!this.hasPermissionDeniedConfig(settings)) {
3537
+ if (!settings.hooks.PermissionDenied) {
3538
+ settings.hooks.PermissionDenied = [];
3539
+ }
3540
+ settings.hooks.PermissionDenied.push({
3541
+ matcher: "",
3542
+ hooks: [{ type: "command", command: PERMISSION_DENIED_HOOK_COMMAND }]
3543
+ });
3544
+ changed = true;
3545
+ }
3267
3546
  if (changed) {
3268
3547
  await this.writeClaudeSettings(settings);
3269
3548
  } else {
@@ -3278,6 +3557,9 @@ var HookInstaller = class {
3278
3557
  if (!settings.hooks) return;
3279
3558
  this.removeHookCommand(settings, "PreToolUse", HOOK_COMMAND);
3280
3559
  this.removeHookCommand(settings, "PermissionRequest", PERMISSION_ACCEPT_COMMAND);
3560
+ this.removeHookCommand(settings, "PreCompact", COMPACT_HOOK_COMMAND);
3561
+ this.removeHookCommand(settings, "PostCompact", POST_COMPACT_HOOK_COMMAND);
3562
+ this.removeHookCommand(settings, "PermissionDenied", PERMISSION_DENIED_HOOK_COMMAND);
3281
3563
  if (Object.keys(settings.hooks).length === 0) {
3282
3564
  delete settings.hooks;
3283
3565
  }
@@ -3315,7 +3597,7 @@ var HookInstaller = class {
3315
3597
  * 检查 settings 中是否已包含所有 Sessix hook 配置
3316
3598
  */
3317
3599
  hasHookConfig(settings) {
3318
- return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
3600
+ return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings) && this.hasPreCompactConfig(settings) && this.hasPostCompactConfig(settings) && this.hasPermissionDeniedConfig(settings);
3319
3601
  }
3320
3602
  /** 检查 PreToolUse 中是否有 approval-hook.js */
3321
3603
  hasPreToolUseConfig(settings) {
@@ -3325,6 +3607,18 @@ var HookInstaller = class {
3325
3607
  hasPermissionRequestConfig(settings) {
3326
3608
  return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
3327
3609
  }
3610
+ /** 检查 PreCompact 中是否有 compact-hook.js */
3611
+ hasPreCompactConfig(settings) {
3612
+ return this.hasHookEntry(settings?.hooks?.PreCompact, COMPACT_HOOK_COMMAND);
3613
+ }
3614
+ /** 检查 PostCompact 中是否有 post-compact-hook.js */
3615
+ hasPostCompactConfig(settings) {
3616
+ return this.hasHookEntry(settings?.hooks?.PostCompact, POST_COMPACT_HOOK_COMMAND);
3617
+ }
3618
+ /** 检查 PermissionDenied 中是否有 permission-denied-hook.js */
3619
+ hasPermissionDeniedConfig(settings) {
3620
+ return this.hasHookEntry(settings?.hooks?.PermissionDenied, PERMISSION_DENIED_HOOK_COMMAND);
3621
+ }
3328
3622
  /** 检查 hook 数组中是否包含指定命令 */
3329
3623
  hasHookEntry(hookArray, command) {
3330
3624
  if (!Array.isArray(hookArray)) return false;
@@ -5054,6 +5348,7 @@ async function start(opts = {}) {
5054
5348
  onStateChange: (state) => mdnsService?.updatePairingState(state)
5055
5349
  });
5056
5350
  approvalProxy.setPairingManager(pairingManager);
5351
+ sessionManager.attachApprovalProxy(approvalProxy);
5057
5352
  const authManager = new AuthManager();
5058
5353
  authManager.on("login_url", (url) => {
5059
5354
  wsBridge.broadcast({ type: "auth_login_url", url });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessix-server",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "bin": {
5
5
  "sessix-server": "dist/index.js"
6
6
  },