sessix-server 0.3.5 → 0.3.7

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
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_node_os8 = require("os");
28
28
  var import_node_fs4 = require("fs");
29
29
  var import_node_path7 = require("path");
30
- var import_node_child_process9 = require("child_process");
30
+ var import_node_child_process10 = require("child_process");
31
31
 
32
32
  // src/i18n/locales/zh.ts
33
33
  var zh = {
@@ -306,7 +306,7 @@ var import_uuid6 = require("uuid");
306
306
  var import_promises4 = require("fs/promises");
307
307
  var import_node_os7 = require("os");
308
308
  var import_node_path6 = require("path");
309
- var import_node_child_process8 = require("child_process");
309
+ var import_node_child_process9 = require("child_process");
310
310
  var import_node_util = require("util");
311
311
 
312
312
  // src/providers/ProcessProvider.ts
@@ -403,7 +403,7 @@ var ProcessProvider = class {
403
403
  * 并开始监听 stdout 的 NDJSON 输出。
404
404
  */
405
405
  async startSession(opts) {
406
- const { projectPath, message, sessionId: existingSessionId, model, permissionMode, effort, images } = opts;
406
+ const { projectPath, message, sessionId: existingSessionId, model, permissionMode, effort, images, fallbackModel, maxBudgetUsd } = opts;
407
407
  const sessionId = existingSessionId ?? (0, import_uuid.v4)();
408
408
  if (this.activeSessions.has(sessionId)) {
409
409
  await this.killSession(sessionId);
@@ -419,10 +419,10 @@ var ProcessProvider = class {
419
419
  summary: message.slice(0, 80)
420
420
  };
421
421
  const resume = opts.resume ?? !!existingSessionId;
422
- const proc = this.spawnClaudeProcess(sessionId, projectPath, resume, model, permissionMode, effort);
422
+ const proc = this.spawnClaudeProcess(sessionId, projectPath, resume, model, permissionMode, effort, fallbackModel, maxBudgetUsd);
423
423
  this.writeUserMessage(proc, message, sessionId, images);
424
424
  session.pid = proc.pid;
425
- this.activeSessions.set(sessionId, { session, process: proc, model, permissionMode, effort });
425
+ this.activeSessions.set(sessionId, { session, process: proc, model, permissionMode, effort, fallbackModel, maxBudgetUsd });
426
426
  proc.on("error", (err) => {
427
427
  console.error(`[ProcessProvider] Session ${sessionId} process error:`, err.message);
428
428
  this.activeSessions.delete(sessionId);
@@ -494,7 +494,7 @@ var ProcessProvider = class {
494
494
  }
495
495
  const savedPendingQuestion = entry.pendingQuestion;
496
496
  const newMode = permissionMode ?? entry.permissionMode;
497
- const proc = this.spawnClaudeProcess(sessionId, entry.session.projectPath, true, entry.model, newMode, entry.effort);
497
+ const proc = this.spawnClaudeProcess(sessionId, entry.session.projectPath, true, entry.model, newMode, entry.effort, entry.fallbackModel, entry.maxBudgetUsd);
498
498
  this.writeUserMessage(proc, message, sessionId, images);
499
499
  entry.session.status = "running";
500
500
  entry.session.lastActiveAt = Date.now();
@@ -544,14 +544,15 @@ var ProcessProvider = class {
544
544
  /**
545
545
  * 启动 claude CLI 进程(持久模式,stdin 保持开放接收多条消息)
546
546
  */
547
- spawnClaudeProcess(sessionId, projectPath, resume = false, model, permissionMode, effort) {
547
+ spawnClaudeProcess(sessionId, projectPath, resume = false, model, permissionMode, effort, fallbackModel, maxBudgetUsd) {
548
548
  const args = [
549
549
  "--input-format",
550
550
  "stream-json",
551
551
  "--output-format",
552
552
  "stream-json",
553
553
  "--verbose",
554
- "--include-partial-messages"
554
+ "--include-partial-messages",
555
+ "--include-hook-events"
555
556
  ];
556
557
  if (resume) {
557
558
  args.push("--resume", sessionId);
@@ -567,6 +568,12 @@ var ProcessProvider = class {
567
568
  if (effort) {
568
569
  args.push("--effort", effort);
569
570
  }
571
+ if (fallbackModel) {
572
+ args.push("--fallback-model", fallbackModel);
573
+ }
574
+ if (maxBudgetUsd != null) {
575
+ args.push("--max-budget-usd", String(maxBudgetUsd));
576
+ }
570
577
  const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
571
578
  delete env.CLAUDECODE;
572
579
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
@@ -1677,7 +1684,7 @@ var SessionManager = class {
1677
1684
  * 调用 provider.startSession(),订阅事件流,
1678
1685
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
1679
1686
  */
1680
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType) {
1687
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType, fallbackModel, maxBudgetUsd) {
1681
1688
  let resolvedAgentType = agentType ?? "claude-code";
1682
1689
  if (!agentType && resumeSessionId && this.providerFactory) {
1683
1690
  const codexProvider = this.providerFactory.getProvider("codex");
@@ -1694,7 +1701,9 @@ var SessionManager = class {
1694
1701
  model,
1695
1702
  permissionMode,
1696
1703
  effort,
1697
- images
1704
+ images,
1705
+ fallbackModel,
1706
+ maxBudgetUsd
1698
1707
  });
1699
1708
  this.sessionAgentType.set(session.id, resolvedAgentType);
1700
1709
  this.lastBroadcastStatus.set(session.id, session.status);
@@ -3275,6 +3284,8 @@ var NotificationService = class {
3275
3284
  latestAssistantText = /* @__PURE__ */ new Map();
3276
3285
  /** 获取全局待审批总数的回调(跨所有会话) */
3277
3286
  globalPendingCountProvider = null;
3287
+ /** 获取当前 WS 连接数的回调(用于在线抑制推送) */
3288
+ connectionCountProvider = null;
3278
3289
  /** 添加通知渠道(id 唯一,可用于后续动态开关) */
3279
3290
  addChannel(id, channel, enabled = true) {
3280
3291
  this.channelMap.set(id, { channel, enabled });
@@ -3312,6 +3323,10 @@ var NotificationService = class {
3312
3323
  setGlobalPendingCountProvider(provider) {
3313
3324
  this.globalPendingCountProvider = provider;
3314
3325
  }
3326
+ /** 设置 WS 连接数提供者(用于 status_change 通知的在线抑制) */
3327
+ setConnectionCountProvider(provider) {
3328
+ this.connectionCountProvider = provider;
3329
+ }
3315
3330
  /** 获取全局待审批总数 */
3316
3331
  getGlobalPendingCount() {
3317
3332
  return this.globalPendingCountProvider?.() ?? 0;
@@ -3454,7 +3469,7 @@ var NotificationService = class {
3454
3469
  isYoloMode,
3455
3470
  updatedAt: Date.now()
3456
3471
  });
3457
- } else {
3472
+ } else if ((this.connectionCountProvider?.() ?? 0) === 0) {
3458
3473
  this.notify({
3459
3474
  title: sessionTitle,
3460
3475
  body,
@@ -3476,7 +3491,7 @@ var NotificationService = class {
3476
3491
  isYoloMode,
3477
3492
  updatedAt: Date.now()
3478
3493
  });
3479
- } else {
3494
+ } else if ((this.connectionCountProvider?.() ?? 0) === 0) {
3480
3495
  this.notify({
3481
3496
  title: sessionTitle,
3482
3497
  body,
@@ -4410,10 +4425,50 @@ var TerminalExecutor = class {
4410
4425
  }
4411
4426
  };
4412
4427
 
4428
+ // src/utils/cliCapabilities.ts
4429
+ var import_node_child_process8 = require("child_process");
4430
+ var DEFAULT_CAPABILITIES = {
4431
+ effortLevels: ["low", "medium", "high", "xhigh", "max"]
4432
+ };
4433
+ async function parseCliCapabilities() {
4434
+ const claudePath = findClaudePath();
4435
+ const [helpText, version] = await Promise.all([
4436
+ runCli(claudePath, ["--help"]),
4437
+ runCli(claudePath, ["--version"])
4438
+ ]);
4439
+ const capabilities = { ...DEFAULT_CAPABILITIES };
4440
+ if (version) {
4441
+ capabilities.version = version.trim();
4442
+ }
4443
+ if (helpText) {
4444
+ const effortMatch = helpText.match(/--effort\s.*?\(([^)]+)\)/);
4445
+ if (effortMatch) {
4446
+ const levels = effortMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
4447
+ if (levels.length > 0) {
4448
+ capabilities.effortLevels = levels;
4449
+ }
4450
+ }
4451
+ }
4452
+ console.log(`[CliCapabilities] version=${capabilities.version ?? "unknown"}, effort=[${capabilities.effortLevels.join(", ")}]`);
4453
+ return capabilities;
4454
+ }
4455
+ function runCli(path2, args) {
4456
+ return new Promise((resolve) => {
4457
+ (0, import_node_child_process8.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
4458
+ if (err) {
4459
+ console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
4460
+ resolve(null);
4461
+ } else {
4462
+ resolve(stdout);
4463
+ }
4464
+ });
4465
+ });
4466
+ }
4467
+
4413
4468
  // src/server.ts
4414
4469
  var WS_PORT = 3745;
4415
4470
  var HTTP_PORT = 3746;
4416
- var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
4471
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process9.exec);
4417
4472
  async function killPortProcess(port) {
4418
4473
  try {
4419
4474
  if (isWindows) {
@@ -4535,6 +4590,12 @@ async function start(opts = {}) {
4535
4590
  notificationService.setGlobalPendingCountProvider(
4536
4591
  () => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
4537
4592
  );
4593
+ notificationService.setConnectionCountProvider(() => wsBridge.getConnectionCount());
4594
+ let cliCapabilities = null;
4595
+ parseCliCapabilities().then((caps) => {
4596
+ cliCapabilities = caps;
4597
+ }).catch(() => {
4598
+ });
4538
4599
  const broadcastUnreadSessions = () => {
4539
4600
  wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
4540
4601
  };
@@ -4556,6 +4617,9 @@ async function start(opts = {}) {
4556
4617
  if (unreadSessionIds.size > 0) {
4557
4618
  wsBridge.send(ws, { type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
4558
4619
  }
4620
+ if (cliCapabilities) {
4621
+ wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version });
4622
+ }
4559
4623
  });
4560
4624
  wsBridge.onClientEvent(async (event, ws) => {
4561
4625
  try {
@@ -4573,7 +4637,9 @@ async function start(opts = {}) {
4573
4637
  event.permissionMode,
4574
4638
  event.effort,
4575
4639
  event.images,
4576
- event.agentType
4640
+ event.agentType,
4641
+ event.fallbackModel,
4642
+ event.maxBudgetUsd
4577
4643
  );
4578
4644
  wsBridge.broadcast({
4579
4645
  type: "session_list",
@@ -5076,7 +5142,7 @@ async function autoUpdateIfNeeded() {
5076
5142
  console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
5077
5143
  console.log();
5078
5144
  try {
5079
- (0, import_node_child_process9.execFileSync)("npx", [`sessix-server@${latest}`], {
5145
+ (0, import_node_child_process10.execFileSync)("npx", [`sessix-server@${latest}`], {
5080
5146
  stdio: "inherit",
5081
5147
  env: { ...process.env, __SESSIX_UPDATED: "1" }
5082
5148
  });
package/dist/server.js CHANGED
@@ -311,7 +311,7 @@ var import_uuid6 = require("uuid");
311
311
  var import_promises4 = require("fs/promises");
312
312
  var import_node_os7 = require("os");
313
313
  var import_node_path6 = require("path");
314
- var import_node_child_process8 = require("child_process");
314
+ var import_node_child_process9 = require("child_process");
315
315
  var import_node_util = require("util");
316
316
 
317
317
  // src/providers/ProcessProvider.ts
@@ -408,7 +408,7 @@ var ProcessProvider = class {
408
408
  * 并开始监听 stdout 的 NDJSON 输出。
409
409
  */
410
410
  async startSession(opts) {
411
- const { projectPath, message, sessionId: existingSessionId, model, permissionMode, effort, images } = opts;
411
+ const { projectPath, message, sessionId: existingSessionId, model, permissionMode, effort, images, fallbackModel, maxBudgetUsd } = opts;
412
412
  const sessionId = existingSessionId ?? (0, import_uuid.v4)();
413
413
  if (this.activeSessions.has(sessionId)) {
414
414
  await this.killSession(sessionId);
@@ -424,10 +424,10 @@ var ProcessProvider = class {
424
424
  summary: message.slice(0, 80)
425
425
  };
426
426
  const resume = opts.resume ?? !!existingSessionId;
427
- const proc = this.spawnClaudeProcess(sessionId, projectPath, resume, model, permissionMode, effort);
427
+ const proc = this.spawnClaudeProcess(sessionId, projectPath, resume, model, permissionMode, effort, fallbackModel, maxBudgetUsd);
428
428
  this.writeUserMessage(proc, message, sessionId, images);
429
429
  session.pid = proc.pid;
430
- this.activeSessions.set(sessionId, { session, process: proc, model, permissionMode, effort });
430
+ this.activeSessions.set(sessionId, { session, process: proc, model, permissionMode, effort, fallbackModel, maxBudgetUsd });
431
431
  proc.on("error", (err) => {
432
432
  console.error(`[ProcessProvider] Session ${sessionId} process error:`, err.message);
433
433
  this.activeSessions.delete(sessionId);
@@ -499,7 +499,7 @@ var ProcessProvider = class {
499
499
  }
500
500
  const savedPendingQuestion = entry.pendingQuestion;
501
501
  const newMode = permissionMode ?? entry.permissionMode;
502
- const proc = this.spawnClaudeProcess(sessionId, entry.session.projectPath, true, entry.model, newMode, entry.effort);
502
+ const proc = this.spawnClaudeProcess(sessionId, entry.session.projectPath, true, entry.model, newMode, entry.effort, entry.fallbackModel, entry.maxBudgetUsd);
503
503
  this.writeUserMessage(proc, message, sessionId, images);
504
504
  entry.session.status = "running";
505
505
  entry.session.lastActiveAt = Date.now();
@@ -549,14 +549,15 @@ var ProcessProvider = class {
549
549
  /**
550
550
  * 启动 claude CLI 进程(持久模式,stdin 保持开放接收多条消息)
551
551
  */
552
- spawnClaudeProcess(sessionId, projectPath, resume = false, model, permissionMode, effort) {
552
+ spawnClaudeProcess(sessionId, projectPath, resume = false, model, permissionMode, effort, fallbackModel, maxBudgetUsd) {
553
553
  const args = [
554
554
  "--input-format",
555
555
  "stream-json",
556
556
  "--output-format",
557
557
  "stream-json",
558
558
  "--verbose",
559
- "--include-partial-messages"
559
+ "--include-partial-messages",
560
+ "--include-hook-events"
560
561
  ];
561
562
  if (resume) {
562
563
  args.push("--resume", sessionId);
@@ -572,6 +573,12 @@ var ProcessProvider = class {
572
573
  if (effort) {
573
574
  args.push("--effort", effort);
574
575
  }
576
+ if (fallbackModel) {
577
+ args.push("--fallback-model", fallbackModel);
578
+ }
579
+ if (maxBudgetUsd != null) {
580
+ args.push("--max-budget-usd", String(maxBudgetUsd));
581
+ }
575
582
  const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
576
583
  delete env.CLAUDECODE;
577
584
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
@@ -1682,7 +1689,7 @@ var SessionManager = class {
1682
1689
  * 调用 provider.startSession(),订阅事件流,
1683
1690
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
1684
1691
  */
1685
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType) {
1692
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType, fallbackModel, maxBudgetUsd) {
1686
1693
  let resolvedAgentType = agentType ?? "claude-code";
1687
1694
  if (!agentType && resumeSessionId && this.providerFactory) {
1688
1695
  const codexProvider = this.providerFactory.getProvider("codex");
@@ -1699,7 +1706,9 @@ var SessionManager = class {
1699
1706
  model,
1700
1707
  permissionMode,
1701
1708
  effort,
1702
- images
1709
+ images,
1710
+ fallbackModel,
1711
+ maxBudgetUsd
1703
1712
  });
1704
1713
  this.sessionAgentType.set(session.id, resolvedAgentType);
1705
1714
  this.lastBroadcastStatus.set(session.id, session.status);
@@ -3280,6 +3289,8 @@ var NotificationService = class {
3280
3289
  latestAssistantText = /* @__PURE__ */ new Map();
3281
3290
  /** 获取全局待审批总数的回调(跨所有会话) */
3282
3291
  globalPendingCountProvider = null;
3292
+ /** 获取当前 WS 连接数的回调(用于在线抑制推送) */
3293
+ connectionCountProvider = null;
3283
3294
  /** 添加通知渠道(id 唯一,可用于后续动态开关) */
3284
3295
  addChannel(id, channel, enabled = true) {
3285
3296
  this.channelMap.set(id, { channel, enabled });
@@ -3317,6 +3328,10 @@ var NotificationService = class {
3317
3328
  setGlobalPendingCountProvider(provider) {
3318
3329
  this.globalPendingCountProvider = provider;
3319
3330
  }
3331
+ /** 设置 WS 连接数提供者(用于 status_change 通知的在线抑制) */
3332
+ setConnectionCountProvider(provider) {
3333
+ this.connectionCountProvider = provider;
3334
+ }
3320
3335
  /** 获取全局待审批总数 */
3321
3336
  getGlobalPendingCount() {
3322
3337
  return this.globalPendingCountProvider?.() ?? 0;
@@ -3459,7 +3474,7 @@ var NotificationService = class {
3459
3474
  isYoloMode,
3460
3475
  updatedAt: Date.now()
3461
3476
  });
3462
- } else {
3477
+ } else if ((this.connectionCountProvider?.() ?? 0) === 0) {
3463
3478
  this.notify({
3464
3479
  title: sessionTitle,
3465
3480
  body,
@@ -3481,7 +3496,7 @@ var NotificationService = class {
3481
3496
  isYoloMode,
3482
3497
  updatedAt: Date.now()
3483
3498
  });
3484
- } else {
3499
+ } else if ((this.connectionCountProvider?.() ?? 0) === 0) {
3485
3500
  this.notify({
3486
3501
  title: sessionTitle,
3487
3502
  body,
@@ -4415,10 +4430,50 @@ var TerminalExecutor = class {
4415
4430
  }
4416
4431
  };
4417
4432
 
4433
+ // src/utils/cliCapabilities.ts
4434
+ var import_node_child_process8 = require("child_process");
4435
+ var DEFAULT_CAPABILITIES = {
4436
+ effortLevels: ["low", "medium", "high", "xhigh", "max"]
4437
+ };
4438
+ async function parseCliCapabilities() {
4439
+ const claudePath = findClaudePath();
4440
+ const [helpText, version] = await Promise.all([
4441
+ runCli(claudePath, ["--help"]),
4442
+ runCli(claudePath, ["--version"])
4443
+ ]);
4444
+ const capabilities = { ...DEFAULT_CAPABILITIES };
4445
+ if (version) {
4446
+ capabilities.version = version.trim();
4447
+ }
4448
+ if (helpText) {
4449
+ const effortMatch = helpText.match(/--effort\s.*?\(([^)]+)\)/);
4450
+ if (effortMatch) {
4451
+ const levels = effortMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
4452
+ if (levels.length > 0) {
4453
+ capabilities.effortLevels = levels;
4454
+ }
4455
+ }
4456
+ }
4457
+ console.log(`[CliCapabilities] version=${capabilities.version ?? "unknown"}, effort=[${capabilities.effortLevels.join(", ")}]`);
4458
+ return capabilities;
4459
+ }
4460
+ function runCli(path2, args) {
4461
+ return new Promise((resolve) => {
4462
+ (0, import_node_child_process8.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
4463
+ if (err) {
4464
+ console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
4465
+ resolve(null);
4466
+ } else {
4467
+ resolve(stdout);
4468
+ }
4469
+ });
4470
+ });
4471
+ }
4472
+
4418
4473
  // src/server.ts
4419
4474
  var WS_PORT = 3745;
4420
4475
  var HTTP_PORT = 3746;
4421
- var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
4476
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process9.exec);
4422
4477
  async function killPortProcess(port) {
4423
4478
  try {
4424
4479
  if (isWindows) {
@@ -4540,6 +4595,12 @@ async function start(opts = {}) {
4540
4595
  notificationService.setGlobalPendingCountProvider(
4541
4596
  () => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
4542
4597
  );
4598
+ notificationService.setConnectionCountProvider(() => wsBridge.getConnectionCount());
4599
+ let cliCapabilities = null;
4600
+ parseCliCapabilities().then((caps) => {
4601
+ cliCapabilities = caps;
4602
+ }).catch(() => {
4603
+ });
4543
4604
  const broadcastUnreadSessions = () => {
4544
4605
  wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
4545
4606
  };
@@ -4561,6 +4622,9 @@ async function start(opts = {}) {
4561
4622
  if (unreadSessionIds.size > 0) {
4562
4623
  wsBridge.send(ws, { type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
4563
4624
  }
4625
+ if (cliCapabilities) {
4626
+ wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version });
4627
+ }
4564
4628
  });
4565
4629
  wsBridge.onClientEvent(async (event, ws) => {
4566
4630
  try {
@@ -4578,7 +4642,9 @@ async function start(opts = {}) {
4578
4642
  event.permissionMode,
4579
4643
  event.effort,
4580
4644
  event.images,
4581
- event.agentType
4645
+ event.agentType,
4646
+ event.fallbackModel,
4647
+ event.maxBudgetUsd
4582
4648
  );
4583
4649
  wsBridge.broadcast({
4584
4650
  type: "session_list",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessix-server",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "bin": {
5
5
  "sessix-server": "dist/index.js"
6
6
  },