sessix-server 0.4.3 → 0.4.5
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 +533 -68
- package/dist/server.js +531 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_node_os10 = require("os");
|
|
28
28
|
var import_node_fs4 = require("fs");
|
|
29
29
|
var import_node_path10 = require("path");
|
|
30
|
-
var
|
|
30
|
+
var import_node_child_process13 = require("child_process");
|
|
31
31
|
|
|
32
32
|
// src/i18n/locales/zh.ts
|
|
33
33
|
var zh = {
|
|
@@ -306,7 +306,7 @@ var import_uuid9 = require("uuid");
|
|
|
306
306
|
var import_promises7 = require("fs/promises");
|
|
307
307
|
var import_node_os9 = require("os");
|
|
308
308
|
var import_node_path9 = require("path");
|
|
309
|
-
var
|
|
309
|
+
var import_node_child_process12 = require("child_process");
|
|
310
310
|
var import_node_util3 = require("util");
|
|
311
311
|
|
|
312
312
|
// src/providers/ProcessProvider.ts
|
|
@@ -662,7 +662,24 @@ var ProcessProvider = class {
|
|
|
662
662
|
writeUserMessage(proc, message, sessionId, images) {
|
|
663
663
|
const content = [];
|
|
664
664
|
if (images?.length) {
|
|
665
|
-
|
|
665
|
+
const ALLOWED_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
|
|
666
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
667
|
+
for (let i = 0; i < images.length; i++) {
|
|
668
|
+
const img = images[i];
|
|
669
|
+
if (!ALLOWED_TYPES.has(img.media_type)) {
|
|
670
|
+
if (sessionId) {
|
|
671
|
+
this.emitWriteError(sessionId, `Image #${i + 1} rejected: unsupported media_type "${img.media_type}". Only JPEG/PNG/WebP/GIF are accepted.`);
|
|
672
|
+
}
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const sizeBytes = Math.floor(img.data.length * 0.75);
|
|
676
|
+
if (sizeBytes > MAX_IMAGE_BYTES) {
|
|
677
|
+
if (sessionId) {
|
|
678
|
+
const sizeMb = (sizeBytes / (1024 * 1024)).toFixed(1);
|
|
679
|
+
this.emitWriteError(sessionId, `Image #${i + 1} rejected: ${sizeMb}MB exceeds 5MB per-image limit.`);
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
666
683
|
content.push({
|
|
667
684
|
type: "image",
|
|
668
685
|
source: { type: "base64", media_type: img.media_type, data: img.data }
|
|
@@ -689,6 +706,14 @@ var ProcessProvider = class {
|
|
|
689
706
|
this.emitWriteError(sessionId, `Failed to send message: ${err.message}`);
|
|
690
707
|
}
|
|
691
708
|
});
|
|
709
|
+
if (sessionId) {
|
|
710
|
+
const syntheticUser = {
|
|
711
|
+
type: "user",
|
|
712
|
+
session_id: sessionId,
|
|
713
|
+
message: { role: "user", content }
|
|
714
|
+
};
|
|
715
|
+
this.emitter.emit(this.getEventName(sessionId), syntheticUser);
|
|
716
|
+
}
|
|
692
717
|
}
|
|
693
718
|
/**
|
|
694
719
|
* 发出写入失败的合成错误事件
|
|
@@ -3710,6 +3735,8 @@ var HookInstaller = class {
|
|
|
3710
3735
|
|
|
3711
3736
|
// src/notification/NotificationService.ts
|
|
3712
3737
|
var import_node_path5 = require("path");
|
|
3738
|
+
var RECENT_ACTIVITY_MAX = 6;
|
|
3739
|
+
var ACTIVITY_PUSH_THROTTLE_MS = 2500;
|
|
3713
3740
|
var NotificationService = class {
|
|
3714
3741
|
constructor(sessionManager, expoChannel = null) {
|
|
3715
3742
|
this.sessionManager = sessionManager;
|
|
@@ -3728,6 +3755,14 @@ var NotificationService = class {
|
|
|
3728
3755
|
latestAssistantText = /* @__PURE__ */ new Map();
|
|
3729
3756
|
/** 获取全局待审批总数的回调(跨所有会话) */
|
|
3730
3757
|
globalPendingCountProvider = null;
|
|
3758
|
+
/** sessionId → 最近活动状态(用于 LA content push) */
|
|
3759
|
+
recentActivityState = /* @__PURE__ */ new Map();
|
|
3760
|
+
/** sessionId → 节流定时器(LA content push) */
|
|
3761
|
+
activityPushTimers = /* @__PURE__ */ new Map();
|
|
3762
|
+
/** 上次推送 LA content 的时间戳(用于节流;首次立即推送) */
|
|
3763
|
+
lastActivityPushAt = /* @__PURE__ */ new Map();
|
|
3764
|
+
/** 提供器:根据 sessionId 取该会话的待审批列表(由 server.ts 注入) */
|
|
3765
|
+
pendingApprovalsProvider = null;
|
|
3731
3766
|
/** 添加通知渠道(id 唯一,可用于后续动态开关) */
|
|
3732
3767
|
addChannel(id, channel, enabled = true) {
|
|
3733
3768
|
this.channelMap.set(id, { channel, enabled });
|
|
@@ -3755,11 +3790,24 @@ var NotificationService = class {
|
|
|
3755
3790
|
}
|
|
3756
3791
|
/** 注册 ActivityKit push token(由手机端启动 Live Activity 后上报) */
|
|
3757
3792
|
addActivityPushToken(sessionId, token) {
|
|
3758
|
-
this.activityPushChannel
|
|
3793
|
+
if (!this.activityPushChannel) {
|
|
3794
|
+
console.warn(`[NotificationService] \u26A0\uFE0F \u6536\u5230 LA push token \u4F46 ActivityPushChannel \u672A\u521D\u59CB\u5316 (session=${sessionId.slice(0, 8)}\u2026) \u2014 \u68C0\u67E5 ~/.sessix/apns.json`);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
this.activityPushChannel.addToken(sessionId, token);
|
|
3798
|
+
console.log(`[NotificationService] \u2705 LA push token \u5DF2\u6CE8\u518C (session=${sessionId.slice(0, 8)}\u2026, token=${token.slice(0, 16)}\u2026)`);
|
|
3799
|
+
this.scheduleActivityPush(sessionId, true);
|
|
3759
3800
|
}
|
|
3760
3801
|
/** 移除 ActivityKit push token */
|
|
3761
3802
|
removeActivityPushToken(sessionId) {
|
|
3762
3803
|
this.activityPushChannel?.removeToken(sessionId);
|
|
3804
|
+
this.clearActivityPushTimer(sessionId);
|
|
3805
|
+
this.recentActivityState.delete(sessionId);
|
|
3806
|
+
this.lastActivityPushAt.delete(sessionId);
|
|
3807
|
+
}
|
|
3808
|
+
/** 注入"会话 → 待审批列表"提供器(server.ts 启动时调用) */
|
|
3809
|
+
setPendingApprovalsProvider(fn) {
|
|
3810
|
+
this.pendingApprovalsProvider = fn;
|
|
3763
3811
|
}
|
|
3764
3812
|
/** 设置全局待审批总数提供者 */
|
|
3765
3813
|
setGlobalPendingCountProvider(provider) {
|
|
@@ -3782,12 +3830,15 @@ var NotificationService = class {
|
|
|
3782
3830
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
3783
3831
|
const dangerLevel2 = this.getDangerLevel(request.toolName);
|
|
3784
3832
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
3833
|
+
const recentActivity = this.getRecentActivity(request.sessionId);
|
|
3834
|
+
const latestMessage = recentActivity[recentActivity.length - 1] ?? "";
|
|
3785
3835
|
this.activityPushChannel.updateActivityWithAlert(
|
|
3786
3836
|
request.sessionId,
|
|
3787
3837
|
{
|
|
3788
3838
|
status: "waitingApproval",
|
|
3789
3839
|
sessionTitle,
|
|
3790
|
-
latestMessage
|
|
3840
|
+
latestMessage,
|
|
3841
|
+
recentActivity,
|
|
3791
3842
|
approvalInfo: {
|
|
3792
3843
|
requestId: request.id,
|
|
3793
3844
|
toolName: request.toolName,
|
|
@@ -3800,6 +3851,7 @@ var NotificationService = class {
|
|
|
3800
3851
|
},
|
|
3801
3852
|
{ title, body }
|
|
3802
3853
|
);
|
|
3854
|
+
this.lastActivityPushAt.set(request.sessionId, Date.now());
|
|
3803
3855
|
return;
|
|
3804
3856
|
}
|
|
3805
3857
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
@@ -3833,17 +3885,20 @@ var NotificationService = class {
|
|
|
3833
3885
|
const body = `\u2753 ${request.question.slice(0, 80)}`;
|
|
3834
3886
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
3835
3887
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
3888
|
+
const recentActivity = this.getRecentActivity(request.sessionId);
|
|
3836
3889
|
this.activityPushChannel.updateActivityWithAlert(
|
|
3837
3890
|
request.sessionId,
|
|
3838
3891
|
{
|
|
3839
3892
|
status: "waitingApproval",
|
|
3840
3893
|
sessionTitle,
|
|
3841
3894
|
latestMessage: request.question.slice(0, 80),
|
|
3895
|
+
recentActivity,
|
|
3842
3896
|
isYoloMode,
|
|
3843
3897
|
updatedAt: Date.now()
|
|
3844
3898
|
},
|
|
3845
3899
|
{ title: sessionTitle, body }
|
|
3846
3900
|
);
|
|
3901
|
+
this.lastActivityPushAt.set(request.sessionId, Date.now());
|
|
3847
3902
|
return;
|
|
3848
3903
|
}
|
|
3849
3904
|
this.notify({
|
|
@@ -3877,6 +3932,10 @@ var NotificationService = class {
|
|
|
3877
3932
|
this.unsubscribe = null;
|
|
3878
3933
|
this.yoloModeState.clear();
|
|
3879
3934
|
this.latestAssistantText.clear();
|
|
3935
|
+
for (const timer of this.activityPushTimers.values()) clearTimeout(timer);
|
|
3936
|
+
this.activityPushTimers.clear();
|
|
3937
|
+
this.recentActivityState.clear();
|
|
3938
|
+
this.lastActivityPushAt.clear();
|
|
3880
3939
|
}
|
|
3881
3940
|
// ============================================
|
|
3882
3941
|
// 内部方法
|
|
@@ -3885,15 +3944,20 @@ var NotificationService = class {
|
|
|
3885
3944
|
switch (event.type) {
|
|
3886
3945
|
case "claude_event": {
|
|
3887
3946
|
this.trackAssistantText(event.sessionId, event.event);
|
|
3947
|
+
this.updateRecentActivity(event.sessionId, event.event);
|
|
3948
|
+
this.scheduleActivityPush(event.sessionId);
|
|
3888
3949
|
break;
|
|
3889
3950
|
}
|
|
3890
3951
|
case "claude_events": {
|
|
3891
3952
|
for (const e of event.events) {
|
|
3892
3953
|
this.trackAssistantText(event.sessionId, e);
|
|
3954
|
+
this.updateRecentActivity(event.sessionId, e);
|
|
3893
3955
|
}
|
|
3956
|
+
this.scheduleActivityPush(event.sessionId);
|
|
3894
3957
|
break;
|
|
3895
3958
|
}
|
|
3896
3959
|
case "status_change": {
|
|
3960
|
+
this.clearActivityPushTimer(event.sessionId);
|
|
3897
3961
|
if (event.status === "idle") {
|
|
3898
3962
|
const sessionTitle = this.getSessionTitle(event.sessionId);
|
|
3899
3963
|
const latestMsg = this.latestAssistantText.get(event.sessionId);
|
|
@@ -3904,9 +3968,12 @@ var NotificationService = class {
|
|
|
3904
3968
|
status: "idle",
|
|
3905
3969
|
sessionTitle,
|
|
3906
3970
|
latestMessage: body,
|
|
3971
|
+
recentActivity: this.getRecentActivity(event.sessionId),
|
|
3907
3972
|
isYoloMode,
|
|
3908
3973
|
updatedAt: Date.now()
|
|
3909
3974
|
});
|
|
3975
|
+
this.recentActivityState.delete(event.sessionId);
|
|
3976
|
+
this.lastActivityPushAt.delete(event.sessionId);
|
|
3910
3977
|
} else {
|
|
3911
3978
|
this.notify({
|
|
3912
3979
|
title: sessionTitle,
|
|
@@ -3926,9 +3993,12 @@ var NotificationService = class {
|
|
|
3926
3993
|
status: "error",
|
|
3927
3994
|
sessionTitle,
|
|
3928
3995
|
latestMessage: body,
|
|
3996
|
+
recentActivity: this.getRecentActivity(event.sessionId),
|
|
3929
3997
|
isYoloMode,
|
|
3930
3998
|
updatedAt: Date.now()
|
|
3931
3999
|
});
|
|
4000
|
+
this.recentActivityState.delete(event.sessionId);
|
|
4001
|
+
this.lastActivityPushAt.delete(event.sessionId);
|
|
3932
4002
|
} else {
|
|
3933
4003
|
this.notify({
|
|
3934
4004
|
title: sessionTitle,
|
|
@@ -3970,6 +4040,229 @@ var NotificationService = class {
|
|
|
3970
4040
|
getYoloMode(sessionId) {
|
|
3971
4041
|
return this.yoloModeState.get(sessionId) ?? false;
|
|
3972
4042
|
}
|
|
4043
|
+
// ============================================
|
|
4044
|
+
// Live Activity 内容推送(后台 LA 实时刷新)
|
|
4045
|
+
// ============================================
|
|
4046
|
+
/**
|
|
4047
|
+
* 把一个 ClaudeStreamEvent 折算到 recentActivity 列表里。
|
|
4048
|
+
* 同一 message.id 内多次 assistant 事件视为流式更新,整段重建 currentEntries;
|
|
4049
|
+
* 切换 message.id 视为新 turn,旧条目沉淀到 history。
|
|
4050
|
+
*/
|
|
4051
|
+
updateRecentActivity(sessionId, event) {
|
|
4052
|
+
if (event.type === "result") {
|
|
4053
|
+
const state2 = this.recentActivityState.get(sessionId);
|
|
4054
|
+
if (state2 && state2.currentEntries.length > 0) {
|
|
4055
|
+
state2.history.push(...state2.currentEntries);
|
|
4056
|
+
while (state2.history.length > RECENT_ACTIVITY_MAX) state2.history.shift();
|
|
4057
|
+
state2.currentEntries = [];
|
|
4058
|
+
state2.currentMessageId = null;
|
|
4059
|
+
}
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
4062
|
+
if (event.type !== "assistant") return;
|
|
4063
|
+
const msg = event.message;
|
|
4064
|
+
if (!Array.isArray(msg.content)) return;
|
|
4065
|
+
let state = this.recentActivityState.get(sessionId);
|
|
4066
|
+
if (!state) {
|
|
4067
|
+
state = { history: [], currentMessageId: null, currentEntries: [] };
|
|
4068
|
+
this.recentActivityState.set(sessionId, state);
|
|
4069
|
+
}
|
|
4070
|
+
if (state.currentMessageId !== msg.id) {
|
|
4071
|
+
if (state.currentEntries.length > 0) {
|
|
4072
|
+
state.history.push(...state.currentEntries);
|
|
4073
|
+
while (state.history.length > RECENT_ACTIVITY_MAX) state.history.shift();
|
|
4074
|
+
}
|
|
4075
|
+
state.currentEntries = [];
|
|
4076
|
+
state.currentMessageId = msg.id;
|
|
4077
|
+
}
|
|
4078
|
+
const next = [];
|
|
4079
|
+
for (const block of msg.content) {
|
|
4080
|
+
if (block.type === "text") {
|
|
4081
|
+
const line = this.summarizeText(block.text);
|
|
4082
|
+
if (line.length >= 4) next.push(line);
|
|
4083
|
+
} else if (block.type === "tool_use") {
|
|
4084
|
+
const line = this.summarizeToolCall(block.name, block.input ?? {});
|
|
4085
|
+
if (line) next.push(line);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
state.currentEntries = next;
|
|
4089
|
+
}
|
|
4090
|
+
/** 取该会话当前的 recentActivity(history + currentEntries),保留末尾 N 条 */
|
|
4091
|
+
getRecentActivity(sessionId) {
|
|
4092
|
+
const state = this.recentActivityState.get(sessionId);
|
|
4093
|
+
if (!state) return [];
|
|
4094
|
+
const combined = [...state.history, ...state.currentEntries];
|
|
4095
|
+
return combined.slice(-RECENT_ACTIVITY_MAX);
|
|
4096
|
+
}
|
|
4097
|
+
/** 节流调度 LA content push;首次立即推,后续合并到 throttle window 末尾 */
|
|
4098
|
+
scheduleActivityPush(sessionId, force = false) {
|
|
4099
|
+
if (!this.activityPushChannel?.hasToken(sessionId)) return;
|
|
4100
|
+
const now = Date.now();
|
|
4101
|
+
const last = this.lastActivityPushAt.get(sessionId) ?? 0;
|
|
4102
|
+
const elapsed = now - last;
|
|
4103
|
+
if (force || elapsed >= ACTIVITY_PUSH_THROTTLE_MS) {
|
|
4104
|
+
this.clearActivityPushTimer(sessionId);
|
|
4105
|
+
this.flushActivityPush(sessionId);
|
|
4106
|
+
return;
|
|
4107
|
+
}
|
|
4108
|
+
if (this.activityPushTimers.has(sessionId)) return;
|
|
4109
|
+
const wait = ACTIVITY_PUSH_THROTTLE_MS - elapsed;
|
|
4110
|
+
this.activityPushTimers.set(
|
|
4111
|
+
sessionId,
|
|
4112
|
+
setTimeout(() => {
|
|
4113
|
+
this.activityPushTimers.delete(sessionId);
|
|
4114
|
+
this.flushActivityPush(sessionId);
|
|
4115
|
+
}, wait)
|
|
4116
|
+
);
|
|
4117
|
+
}
|
|
4118
|
+
clearActivityPushTimer(sessionId) {
|
|
4119
|
+
const timer = this.activityPushTimers.get(sessionId);
|
|
4120
|
+
if (timer) {
|
|
4121
|
+
clearTimeout(timer);
|
|
4122
|
+
this.activityPushTimers.delete(sessionId);
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
/** 真正发送一次 LA content push(无 alert) */
|
|
4126
|
+
flushActivityPush(sessionId) {
|
|
4127
|
+
const channel = this.activityPushChannel;
|
|
4128
|
+
if (!channel?.hasToken(sessionId)) return;
|
|
4129
|
+
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
4130
|
+
if (!session) return;
|
|
4131
|
+
const recentActivity = this.getRecentActivity(sessionId);
|
|
4132
|
+
const latestMessage = recentActivity[recentActivity.length - 1] ?? this.latestAssistantText.get(sessionId) ?? "";
|
|
4133
|
+
const sessionTitle = this.getSessionTitle(sessionId);
|
|
4134
|
+
const isYoloMode = this.getYoloMode(sessionId);
|
|
4135
|
+
const pendingApprovals = this.pendingApprovalsProvider?.(sessionId) ?? [];
|
|
4136
|
+
const latestApproval = pendingApprovals[pendingApprovals.length - 1];
|
|
4137
|
+
const status = latestApproval ? "waitingApproval" : this.mapSessionStatus(session.status);
|
|
4138
|
+
const contentState = {
|
|
4139
|
+
status,
|
|
4140
|
+
sessionTitle,
|
|
4141
|
+
latestMessage,
|
|
4142
|
+
recentActivity,
|
|
4143
|
+
isYoloMode,
|
|
4144
|
+
updatedAt: Date.now()
|
|
4145
|
+
};
|
|
4146
|
+
if (latestApproval) {
|
|
4147
|
+
contentState.approvalInfo = {
|
|
4148
|
+
requestId: latestApproval.id,
|
|
4149
|
+
toolName: latestApproval.toolName,
|
|
4150
|
+
description: String(latestApproval.description ?? "").slice(0, 80),
|
|
4151
|
+
dangerLevel: this.getDangerLevel(latestApproval.toolName),
|
|
4152
|
+
pendingCount: pendingApprovals.length
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
if (session.stats) {
|
|
4156
|
+
contentState.stats = {
|
|
4157
|
+
totalInputTokens: session.stats.totalInputTokens,
|
|
4158
|
+
totalOutputTokens: session.stats.totalOutputTokens,
|
|
4159
|
+
totalCostUsd: session.stats.totalCostUsd
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
this.lastActivityPushAt.set(sessionId, Date.now());
|
|
4163
|
+
const lineCount = recentActivity.length;
|
|
4164
|
+
channel.updateActivity(sessionId, contentState).then(() => {
|
|
4165
|
+
console.log(`[NotificationService] \u{1F4E1} LA push \u2713 session=${sessionId.slice(0, 8)}\u2026 status=${status} lines=${lineCount}`);
|
|
4166
|
+
}).catch((err) => {
|
|
4167
|
+
console.warn(`[NotificationService] \u{1F4E1} LA push \u2717 session=${sessionId.slice(0, 8)}\u2026:`, err instanceof Error ? err.message : err);
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
/** SessionStatus → LiveActivity status 字符串映射(与客户端 mapStatus 一致) */
|
|
4171
|
+
mapSessionStatus(status) {
|
|
4172
|
+
switch (status) {
|
|
4173
|
+
case "running":
|
|
4174
|
+
return "running";
|
|
4175
|
+
case "waiting_approval":
|
|
4176
|
+
return "waitingApproval";
|
|
4177
|
+
case "waiting_question":
|
|
4178
|
+
return "waitingQuestion";
|
|
4179
|
+
case "idle":
|
|
4180
|
+
return "completed";
|
|
4181
|
+
case "completed":
|
|
4182
|
+
return "completed";
|
|
4183
|
+
case "error":
|
|
4184
|
+
return "error";
|
|
4185
|
+
default:
|
|
4186
|
+
return "idle";
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
/** 文本块清洗:去多余空白 + 截断到 70 字符 */
|
|
4190
|
+
summarizeText(raw) {
|
|
4191
|
+
if (typeof raw !== "string") return "";
|
|
4192
|
+
const cleaned = raw.replace(/\s+/g, " ").trim();
|
|
4193
|
+
return cleaned.length > 70 ? cleaned.slice(0, 70) + "\u2026" : cleaned;
|
|
4194
|
+
}
|
|
4195
|
+
/** 工具调用摘要(与客户端 summarizeToolCall 行为对齐,简化版只输出中文) */
|
|
4196
|
+
summarizeToolCall(name, input) {
|
|
4197
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
4198
|
+
const baseName = (p) => {
|
|
4199
|
+
const cleaned = p.split(/[?#]/)[0];
|
|
4200
|
+
const parts = cleaned.split("/");
|
|
4201
|
+
return parts[parts.length - 1] || cleaned;
|
|
4202
|
+
};
|
|
4203
|
+
const trunc = (s, n) => s.length > n ? s.slice(0, n) + "\u2026" : s;
|
|
4204
|
+
switch (name) {
|
|
4205
|
+
case "Bash": {
|
|
4206
|
+
const cmd = str(input.command).split("\n")[0];
|
|
4207
|
+
return cmd ? `\u8FD0\u884C: ${trunc(cmd, 60)}` : "\u6267\u884C\u547D\u4EE4";
|
|
4208
|
+
}
|
|
4209
|
+
case "Edit": {
|
|
4210
|
+
const fp = baseName(str(input.file_path));
|
|
4211
|
+
return fp ? `\u7F16\u8F91 ${fp}` : "\u7F16\u8F91\u6587\u4EF6";
|
|
4212
|
+
}
|
|
4213
|
+
case "MultiEdit": {
|
|
4214
|
+
const fp = baseName(str(input.file_path));
|
|
4215
|
+
return fp ? `\u6279\u91CF\u7F16\u8F91 ${fp}` : "\u6279\u91CF\u7F16\u8F91\u6587\u4EF6";
|
|
4216
|
+
}
|
|
4217
|
+
case "Write": {
|
|
4218
|
+
const fp = baseName(str(input.file_path));
|
|
4219
|
+
return fp ? `\u5199\u5165 ${fp}` : "\u5199\u5165\u6587\u4EF6";
|
|
4220
|
+
}
|
|
4221
|
+
case "Read":
|
|
4222
|
+
case "NotebookEdit": {
|
|
4223
|
+
const fp = baseName(str(input.file_path) || str(input.notebook_path));
|
|
4224
|
+
return fp ? `\u9605\u8BFB ${fp}` : "\u9605\u8BFB\u6587\u4EF6";
|
|
4225
|
+
}
|
|
4226
|
+
case "Grep": {
|
|
4227
|
+
const p = str(input.pattern);
|
|
4228
|
+
return p ? `\u641C\u7D22: ${trunc(p, 50)}` : "\u641C\u7D22\u4EE3\u7801";
|
|
4229
|
+
}
|
|
4230
|
+
case "Glob": {
|
|
4231
|
+
const p = str(input.pattern);
|
|
4232
|
+
return p ? `\u67E5\u627E: ${trunc(p, 50)}` : "\u67E5\u627E\u6587\u4EF6";
|
|
4233
|
+
}
|
|
4234
|
+
case "WebFetch": {
|
|
4235
|
+
const url = str(input.url);
|
|
4236
|
+
let host = url;
|
|
4237
|
+
try {
|
|
4238
|
+
host = new URL(url).hostname;
|
|
4239
|
+
} catch {
|
|
4240
|
+
}
|
|
4241
|
+
return host ? `\u8BF7\u6C42 ${trunc(host, 50)}` : "\u8BF7\u6C42\u7F51\u9875";
|
|
4242
|
+
}
|
|
4243
|
+
case "WebSearch": {
|
|
4244
|
+
const q = str(input.query);
|
|
4245
|
+
return q ? `\u641C\u7D22\u7F51\u9875: ${trunc(q, 50)}` : "\u641C\u7D22\u7F51\u9875";
|
|
4246
|
+
}
|
|
4247
|
+
case "TodoWrite":
|
|
4248
|
+
return "\u66F4\u65B0\u4EFB\u52A1\u6E05\u5355";
|
|
4249
|
+
case "Task":
|
|
4250
|
+
case "Agent": {
|
|
4251
|
+
const desc = str(input.description) || str(input.subagent_type);
|
|
4252
|
+
return desc ? `\u6D3E\u53D1\u4EFB\u52A1: ${trunc(desc, 50)}` : "\u6D3E\u53D1\u5B50\u4EFB\u52A1";
|
|
4253
|
+
}
|
|
4254
|
+
case "ExitPlanMode":
|
|
4255
|
+
return "\u63D0\u4EA4\u8BA1\u5212";
|
|
4256
|
+
case "Skill": {
|
|
4257
|
+
const skill = str(input.skill);
|
|
4258
|
+
return skill ? `\u8C03\u7528\u6280\u80FD: ${trunc(skill, 40)}` : "\u8C03\u7528\u6280\u80FD";
|
|
4259
|
+
}
|
|
4260
|
+
default: {
|
|
4261
|
+
const summary = trunc(JSON.stringify(input), 50);
|
|
4262
|
+
return name ? `${name}: ${summary}` : summary;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
3973
4266
|
};
|
|
3974
4267
|
|
|
3975
4268
|
// src/notification/DesktopNotificationChannel.ts
|
|
@@ -4083,62 +4376,82 @@ var ExpoNotificationChannel = class {
|
|
|
4083
4376
|
var http2 = __toESM(require("http2"));
|
|
4084
4377
|
var fs2 = __toESM(require("fs"));
|
|
4085
4378
|
var crypto = __toESM(require("crypto"));
|
|
4379
|
+
var APNS_HOSTS = {
|
|
4380
|
+
production: "api.push.apple.com",
|
|
4381
|
+
sandbox: "api.sandbox.push.apple.com"
|
|
4382
|
+
};
|
|
4086
4383
|
var ActivityPushChannel = class {
|
|
4087
4384
|
/** sessionId -> activityPushToken */
|
|
4088
4385
|
tokens = /* @__PURE__ */ new Map();
|
|
4386
|
+
/**
|
|
4387
|
+
* 每个 token 已确认工作的 APNs 环境。
|
|
4388
|
+
* Debug build (aps-environment=development) 的 token 仅在 sandbox 端有效;
|
|
4389
|
+
* Release build (aps-environment=production) 的 token 仅在 production 端有效。
|
|
4390
|
+
* 同时维护两个连接 + 探测机制,避免环境配错时静默失败。
|
|
4391
|
+
*/
|
|
4392
|
+
tokenEnv = /* @__PURE__ */ new Map();
|
|
4393
|
+
/** 首次探测顺序(来自配置 hint),未配置则先试 sandbox(开发场景占多数) */
|
|
4394
|
+
probeOrder;
|
|
4089
4395
|
teamId;
|
|
4090
4396
|
keyId;
|
|
4091
4397
|
authKey;
|
|
4092
|
-
apnsHost;
|
|
4093
4398
|
/** 缓存的 JWT token + 过期时间 */
|
|
4094
4399
|
cachedJwt = null;
|
|
4095
|
-
/**
|
|
4096
|
-
|
|
4400
|
+
/** 每个环境一条 HTTP/2 长连接 */
|
|
4401
|
+
http2Clients = {};
|
|
4097
4402
|
constructor(config) {
|
|
4098
4403
|
this.teamId = config.teamId;
|
|
4099
4404
|
this.keyId = config.keyId;
|
|
4100
4405
|
this.authKey = fs2.readFileSync(config.authKeyPath, "utf-8");
|
|
4101
|
-
this.
|
|
4102
|
-
console.log(`[ActivityPushChannel] Initialized (${
|
|
4103
|
-
}
|
|
4104
|
-
/**
|
|
4105
|
-
getHttp2Client() {
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4406
|
+
this.probeOrder = config.sandbox === false ? ["production", "sandbox"] : ["sandbox", "production"];
|
|
4407
|
+
console.log(`[ActivityPushChannel] Initialized (probe order: ${this.probeOrder.join(" \u2192 ")})`);
|
|
4408
|
+
}
|
|
4409
|
+
/** 获取或新建指定环境的 HTTP/2 长连接 */
|
|
4410
|
+
getHttp2Client(env) {
|
|
4411
|
+
const existing = this.http2Clients[env];
|
|
4412
|
+
if (existing && !existing.destroyed && !existing.closed) {
|
|
4413
|
+
return existing;
|
|
4414
|
+
}
|
|
4415
|
+
const client = http2.connect(`https://${APNS_HOSTS[env]}`);
|
|
4416
|
+
client.on("error", (err) => {
|
|
4417
|
+
console.warn(`[ActivityPushChannel] HTTP/2 (${env}) error, will reconnect on next request:`, err.message);
|
|
4418
|
+
client.destroy();
|
|
4419
|
+
if (this.http2Clients[env] === client) delete this.http2Clients[env];
|
|
4114
4420
|
});
|
|
4115
|
-
|
|
4116
|
-
this.
|
|
4421
|
+
client.on("close", () => {
|
|
4422
|
+
if (this.http2Clients[env] === client) delete this.http2Clients[env];
|
|
4117
4423
|
});
|
|
4118
|
-
|
|
4424
|
+
this.http2Clients[env] = client;
|
|
4425
|
+
return client;
|
|
4119
4426
|
}
|
|
4120
4427
|
/** 注册 Activity push token */
|
|
4121
4428
|
addToken(sessionId, token) {
|
|
4429
|
+
const existed = this.tokens.has(sessionId);
|
|
4122
4430
|
this.tokens.set(sessionId, token);
|
|
4123
|
-
console.log(`[ActivityPushChannel] Token registered: session=${sessionId}`);
|
|
4431
|
+
console.log(`[ActivityPushChannel] Token ${existed ? "updated" : "registered"}: session=${sessionId.slice(0, 8)}\u2026 token=${token.slice(0, 16)}\u2026`);
|
|
4124
4432
|
}
|
|
4125
4433
|
/** 移除 Activity push token */
|
|
4126
4434
|
removeToken(sessionId) {
|
|
4435
|
+
const tok = this.tokens.get(sessionId);
|
|
4127
4436
|
this.tokens.delete(sessionId);
|
|
4437
|
+
if (tok) this.tokenEnv.delete(tok);
|
|
4128
4438
|
}
|
|
4129
|
-
/** 发送 content-state 更新到指定会话的 Live Activity */
|
|
4439
|
+
/** 发送 content-state 更新到指定会话的 Live Activity(纯内容刷新,不响通知) */
|
|
4130
4440
|
async updateActivity(sessionId, contentState) {
|
|
4131
4441
|
const token = this.tokens.get(sessionId);
|
|
4132
4442
|
if (!token) return;
|
|
4443
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4133
4444
|
const payload = {
|
|
4134
4445
|
aps: {
|
|
4135
|
-
timestamp:
|
|
4446
|
+
timestamp: now,
|
|
4136
4447
|
event: "update",
|
|
4137
|
-
"content-state": contentState
|
|
4448
|
+
"content-state": contentState,
|
|
4449
|
+
// 2 分钟没新内容就让 LA 进入 stale 状态(系统自动灰化),避免显示陈旧数据
|
|
4450
|
+
"stale-date": now + 120
|
|
4138
4451
|
}
|
|
4139
4452
|
};
|
|
4140
4453
|
try {
|
|
4141
|
-
await this.sendToAPNs(token, payload);
|
|
4454
|
+
await this.sendToAPNs(token, payload, { priority: "5" });
|
|
4142
4455
|
} catch (err) {
|
|
4143
4456
|
console.warn(`[ActivityPushChannel] Update failed session=${sessionId}:`, err);
|
|
4144
4457
|
}
|
|
@@ -4147,17 +4460,19 @@ var ActivityPushChannel = class {
|
|
|
4147
4460
|
async updateActivityWithAlert(sessionId, contentState, alert) {
|
|
4148
4461
|
const token = this.tokens.get(sessionId);
|
|
4149
4462
|
if (!token) return;
|
|
4463
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4150
4464
|
const payload = {
|
|
4151
4465
|
aps: {
|
|
4152
|
-
timestamp:
|
|
4466
|
+
timestamp: now,
|
|
4153
4467
|
event: "update",
|
|
4154
4468
|
"content-state": contentState,
|
|
4469
|
+
"stale-date": now + 120,
|
|
4155
4470
|
alert,
|
|
4156
4471
|
sound: "default"
|
|
4157
4472
|
}
|
|
4158
4473
|
};
|
|
4159
4474
|
try {
|
|
4160
|
-
await this.sendToAPNs(token, payload);
|
|
4475
|
+
await this.sendToAPNs(token, payload, { priority: "10" });
|
|
4161
4476
|
} catch (err) {
|
|
4162
4477
|
console.warn(`[ActivityPushChannel] Alert update failed session=${sessionId}:`, err);
|
|
4163
4478
|
}
|
|
@@ -4166,15 +4481,16 @@ var ActivityPushChannel = class {
|
|
|
4166
4481
|
async endActivity(sessionId, contentState) {
|
|
4167
4482
|
const token = this.tokens.get(sessionId);
|
|
4168
4483
|
if (!token) return;
|
|
4484
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4169
4485
|
const payload = {
|
|
4170
4486
|
aps: {
|
|
4171
|
-
timestamp:
|
|
4487
|
+
timestamp: now,
|
|
4172
4488
|
event: "end",
|
|
4173
4489
|
"content-state": contentState
|
|
4174
4490
|
}
|
|
4175
4491
|
};
|
|
4176
4492
|
try {
|
|
4177
|
-
await this.sendToAPNs(token, payload);
|
|
4493
|
+
await this.sendToAPNs(token, payload, { priority: "10" });
|
|
4178
4494
|
} catch (err) {
|
|
4179
4495
|
console.warn(`[ActivityPushChannel] End failed session=${sessionId}:`, err);
|
|
4180
4496
|
}
|
|
@@ -4184,15 +4500,44 @@ var ActivityPushChannel = class {
|
|
|
4184
4500
|
hasToken(sessionId) {
|
|
4185
4501
|
return this.tokens.has(sessionId);
|
|
4186
4502
|
}
|
|
4187
|
-
/**
|
|
4188
|
-
|
|
4503
|
+
/**
|
|
4504
|
+
* 发送 APNs,自动处理环境探测。
|
|
4505
|
+
* 对每个 token:先用已确认的环境(如有);否则按 probeOrder 顺序探测,
|
|
4506
|
+
* 收到 BadDeviceToken 自动切到另一个环境,并把成功的环境绑定到该 token。
|
|
4507
|
+
*/
|
|
4508
|
+
async sendToAPNs(deviceToken, payload, opts = {}) {
|
|
4509
|
+
const known = this.tokenEnv.get(deviceToken);
|
|
4510
|
+
if (known) {
|
|
4511
|
+
return this.sendToAPNsOnce(deviceToken, payload, opts, known);
|
|
4512
|
+
}
|
|
4513
|
+
let lastErr = null;
|
|
4514
|
+
for (const env of this.probeOrder) {
|
|
4515
|
+
try {
|
|
4516
|
+
await this.sendToAPNsOnce(deviceToken, payload, opts, env);
|
|
4517
|
+
this.tokenEnv.set(deviceToken, env);
|
|
4518
|
+
if (env !== this.probeOrder[0]) {
|
|
4519
|
+
console.log(`[ActivityPushChannel] Token bound to ${env} after probe (token=${deviceToken.slice(0, 16)}\u2026)`);
|
|
4520
|
+
}
|
|
4521
|
+
return;
|
|
4522
|
+
} catch (err) {
|
|
4523
|
+
lastErr = err;
|
|
4524
|
+
if (!isBadDeviceTokenError(err)) {
|
|
4525
|
+
throw err;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
throw lastErr ?? new Error("APNs send failed: all environments rejected token");
|
|
4530
|
+
}
|
|
4531
|
+
/** 单次 APNs HTTP/2 请求(内部 helper,不做探测) */
|
|
4532
|
+
async sendToAPNsOnce(deviceToken, payload, opts, env) {
|
|
4189
4533
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
4190
4534
|
const jwt = this.getJWT();
|
|
4191
4535
|
const payloadStr = JSON.stringify(payload);
|
|
4536
|
+
const priority = opts.priority ?? "10";
|
|
4192
4537
|
return new Promise((resolve, reject) => {
|
|
4193
4538
|
let client;
|
|
4194
4539
|
try {
|
|
4195
|
-
client = this.getHttp2Client();
|
|
4540
|
+
client = this.getHttp2Client(env);
|
|
4196
4541
|
} catch (err) {
|
|
4197
4542
|
return reject(err);
|
|
4198
4543
|
}
|
|
@@ -4202,7 +4547,7 @@ var ActivityPushChannel = class {
|
|
|
4202
4547
|
"authorization": `bearer ${jwt}`,
|
|
4203
4548
|
"apns-topic": topic,
|
|
4204
4549
|
"apns-push-type": "liveactivity",
|
|
4205
|
-
"apns-priority":
|
|
4550
|
+
"apns-priority": priority,
|
|
4206
4551
|
"apns-expiration": String(Math.floor(Date.now() / 1e3) + 30),
|
|
4207
4552
|
"content-type": "application/json",
|
|
4208
4553
|
"content-length": Buffer.byteLength(payloadStr)
|
|
@@ -4220,10 +4565,11 @@ var ActivityPushChannel = class {
|
|
|
4220
4565
|
resolve();
|
|
4221
4566
|
} else {
|
|
4222
4567
|
if (statusCode === 0) {
|
|
4223
|
-
this.
|
|
4224
|
-
|
|
4568
|
+
const c = this.http2Clients[env];
|
|
4569
|
+
c?.destroy();
|
|
4570
|
+
delete this.http2Clients[env];
|
|
4225
4571
|
}
|
|
4226
|
-
reject(new
|
|
4572
|
+
reject(new ApnsError(statusCode, responseData));
|
|
4227
4573
|
}
|
|
4228
4574
|
});
|
|
4229
4575
|
req.on("error", (err) => {
|
|
@@ -4256,6 +4602,24 @@ var ActivityPushChannel = class {
|
|
|
4256
4602
|
return token;
|
|
4257
4603
|
}
|
|
4258
4604
|
};
|
|
4605
|
+
var ApnsError = class extends Error {
|
|
4606
|
+
constructor(statusCode, responseBody) {
|
|
4607
|
+
super(`APNs returned ${statusCode}: ${responseBody}`);
|
|
4608
|
+
this.statusCode = statusCode;
|
|
4609
|
+
this.responseBody = responseBody;
|
|
4610
|
+
this.name = "ApnsError";
|
|
4611
|
+
}
|
|
4612
|
+
};
|
|
4613
|
+
function isBadDeviceTokenError(err) {
|
|
4614
|
+
if (!(err instanceof ApnsError)) return false;
|
|
4615
|
+
if (err.statusCode !== 400 && err.statusCode !== 410) return false;
|
|
4616
|
+
try {
|
|
4617
|
+
const parsed = JSON.parse(err.responseBody);
|
|
4618
|
+
return parsed.reason === "BadDeviceToken" || parsed.reason === "Unregistered";
|
|
4619
|
+
} catch {
|
|
4620
|
+
return false;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4259
4623
|
|
|
4260
4624
|
// src/session/ProjectReader.ts
|
|
4261
4625
|
var import_promises3 = require("fs/promises");
|
|
@@ -4655,6 +5019,45 @@ var PairingManager = class {
|
|
|
4655
5019
|
}
|
|
4656
5020
|
};
|
|
4657
5021
|
|
|
5022
|
+
// src/utils/shellPath.ts
|
|
5023
|
+
var import_node_child_process7 = require("child_process");
|
|
5024
|
+
var fixed = false;
|
|
5025
|
+
function fixShellPath() {
|
|
5026
|
+
if (fixed || isWindows) {
|
|
5027
|
+
fixed = true;
|
|
5028
|
+
return;
|
|
5029
|
+
}
|
|
5030
|
+
fixed = true;
|
|
5031
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
5032
|
+
const isFish = /\/fish$/.test(shell);
|
|
5033
|
+
const printPathCmd = isFish ? "string join : $PATH" : 'printf "%s" "$PATH"';
|
|
5034
|
+
let raw;
|
|
5035
|
+
try {
|
|
5036
|
+
raw = (0, import_node_child_process7.execFileSync)(shell, ["-l", "-c", printPathCmd], {
|
|
5037
|
+
encoding: "utf8",
|
|
5038
|
+
timeout: 3e3,
|
|
5039
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5040
|
+
});
|
|
5041
|
+
} catch (err) {
|
|
5042
|
+
console.warn("[fixShellPath] failed to read login shell PATH:", err);
|
|
5043
|
+
return;
|
|
5044
|
+
}
|
|
5045
|
+
const fromShell = raw.trim();
|
|
5046
|
+
if (!fromShell) return;
|
|
5047
|
+
process.env.PATH = mergePath(fromShell, process.env.PATH || "");
|
|
5048
|
+
}
|
|
5049
|
+
function mergePath(primary, secondary) {
|
|
5050
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5051
|
+
const out = [];
|
|
5052
|
+
for (const seg of primary.split(":").concat(secondary.split(":"))) {
|
|
5053
|
+
if (!seg) continue;
|
|
5054
|
+
if (seen.has(seg)) continue;
|
|
5055
|
+
seen.add(seg);
|
|
5056
|
+
out.push(seg);
|
|
5057
|
+
}
|
|
5058
|
+
return out.join(":");
|
|
5059
|
+
}
|
|
5060
|
+
|
|
4658
5061
|
// src/auth/AuthManager.ts
|
|
4659
5062
|
var import_child_process3 = require("child_process");
|
|
4660
5063
|
var import_child_process4 = require("child_process");
|
|
@@ -4777,9 +5180,9 @@ var AuthManager = class extends import_events3.EventEmitter {
|
|
|
4777
5180
|
var import_promises8 = require("fs/promises");
|
|
4778
5181
|
|
|
4779
5182
|
// src/terminal/TerminalExecutor.ts
|
|
4780
|
-
var
|
|
5183
|
+
var import_node_child_process8 = require("child_process");
|
|
4781
5184
|
var import_uuid5 = require("uuid");
|
|
4782
|
-
var EXEC_TIMEOUT_MS =
|
|
5185
|
+
var EXEC_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4783
5186
|
var TerminalExecutor = class {
|
|
4784
5187
|
processes = /* @__PURE__ */ new Map();
|
|
4785
5188
|
eventCallbacks = [];
|
|
@@ -4801,9 +5204,9 @@ var TerminalExecutor = class {
|
|
|
4801
5204
|
}
|
|
4802
5205
|
exec(sessionId, command, cwd) {
|
|
4803
5206
|
const execId = (0, import_uuid5.v4)();
|
|
4804
|
-
const shell = isWindows ? "powershell" : "
|
|
4805
|
-
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
4806
|
-
const proc = (0,
|
|
5207
|
+
const shell = isWindows ? "powershell" : process.env.SHELL || "/bin/zsh";
|
|
5208
|
+
const args = isWindows ? ["-Command", command] : ["-l", "-c", command];
|
|
5209
|
+
const proc = (0, import_node_child_process8.spawn)(shell, args, {
|
|
4807
5210
|
cwd,
|
|
4808
5211
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4809
5212
|
env: { ...process.env }
|
|
@@ -4840,6 +5243,14 @@ var TerminalExecutor = class {
|
|
|
4840
5243
|
});
|
|
4841
5244
|
const timer = setTimeout(() => {
|
|
4842
5245
|
if (this.processes.has(execId)) {
|
|
5246
|
+
this.emit({
|
|
5247
|
+
type: "terminal_output",
|
|
5248
|
+
sessionId,
|
|
5249
|
+
execId,
|
|
5250
|
+
stream: "stderr",
|
|
5251
|
+
data: `[killed: timeout ${Math.round(EXEC_TIMEOUT_MS / 6e4)}m]
|
|
5252
|
+
`
|
|
5253
|
+
});
|
|
4843
5254
|
killProcessCrossPlatform(proc);
|
|
4844
5255
|
}
|
|
4845
5256
|
}, EXEC_TIMEOUT_MS);
|
|
@@ -4864,13 +5275,13 @@ var TerminalExecutor = class {
|
|
|
4864
5275
|
};
|
|
4865
5276
|
|
|
4866
5277
|
// src/xcode/XcodeBuildExecutor.ts
|
|
4867
|
-
var
|
|
5278
|
+
var import_node_child_process9 = require("child_process");
|
|
4868
5279
|
var import_node_util = require("util");
|
|
4869
5280
|
var import_promises4 = require("fs/promises");
|
|
4870
5281
|
var import_node_path6 = require("path");
|
|
4871
5282
|
var import_node_os7 = require("os");
|
|
4872
5283
|
var import_uuid6 = require("uuid");
|
|
4873
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
5284
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process9.exec);
|
|
4874
5285
|
var BUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4875
5286
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4876
5287
|
var CONFIG_FILE = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix", "xcode-config.json");
|
|
@@ -5059,7 +5470,7 @@ ${e.stderr ?? ""}`);
|
|
|
5059
5470
|
if (override) await this.saveConfig(projectPath, override);
|
|
5060
5471
|
const buildId = (0, import_uuid6.v4)();
|
|
5061
5472
|
const args = buildArgs(config);
|
|
5062
|
-
const proc = (0,
|
|
5473
|
+
const proc = (0, import_node_child_process9.spawn)("xcodebuild", args, {
|
|
5063
5474
|
cwd: projectPath,
|
|
5064
5475
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5065
5476
|
env: { ...process.env, NSUnbufferedIO: "YES" }
|
|
@@ -5155,7 +5566,7 @@ ${e.stderr ?? ""}`);
|
|
|
5155
5566
|
|
|
5156
5567
|
`
|
|
5157
5568
|
});
|
|
5158
|
-
const proc = (0,
|
|
5569
|
+
const proc = (0, import_node_child_process9.spawn)(installCmd[0], installCmd.slice(1), {
|
|
5159
5570
|
cwd: projectPath,
|
|
5160
5571
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5161
5572
|
});
|
|
@@ -5562,15 +5973,16 @@ var CommandDiscovery = class {
|
|
|
5562
5973
|
const cmd = sanitizeBashLine(rawLine);
|
|
5563
5974
|
if (!cmd) continue;
|
|
5564
5975
|
const { command: cleanCmd, inlineComment } = splitInlineComment(cmd);
|
|
5565
|
-
const
|
|
5976
|
+
const { cwd: cdCwd, command: finalCmd } = splitCdPrefix(cleanCmd);
|
|
5977
|
+
const title = synthesizeTitle(finalCmd);
|
|
5566
5978
|
out.push(makeCommand({
|
|
5567
5979
|
title,
|
|
5568
|
-
command:
|
|
5569
|
-
cwd:
|
|
5980
|
+
command: finalCmd,
|
|
5981
|
+
cwd: cdCwd,
|
|
5570
5982
|
source,
|
|
5571
5983
|
sourceFile: fileName,
|
|
5572
5984
|
description: inlineComment ?? blockHeading,
|
|
5573
|
-
category: classifyByCommand(
|
|
5985
|
+
category: classifyByCommand(finalCmd)
|
|
5574
5986
|
}));
|
|
5575
5987
|
}
|
|
5576
5988
|
}
|
|
@@ -5615,6 +6027,19 @@ function synthesizeTitle(cmd) {
|
|
|
5615
6027
|
const head = tokens.slice(0, 3).join(" ");
|
|
5616
6028
|
return head.length > 60 ? head.slice(0, 60) + "\u2026" : head;
|
|
5617
6029
|
}
|
|
6030
|
+
function splitCdPrefix(cmd) {
|
|
6031
|
+
const m = /^cd\s+(\S+)\s*&&\s*(.+)$/.exec(cmd);
|
|
6032
|
+
if (!m) return { cwd: "", command: cmd };
|
|
6033
|
+
const path2 = m[1];
|
|
6034
|
+
if (!path2) return { cwd: "", command: cmd };
|
|
6035
|
+
if (path2.startsWith("/") || path2.startsWith("~") || path2.startsWith("-")) {
|
|
6036
|
+
return { cwd: "", command: cmd };
|
|
6037
|
+
}
|
|
6038
|
+
if (path2.split("/").some((seg) => seg === "..")) {
|
|
6039
|
+
return { cwd: "", command: cmd };
|
|
6040
|
+
}
|
|
6041
|
+
return { cwd: path2, command: m[2].trim() };
|
|
6042
|
+
}
|
|
5618
6043
|
function splitInlineComment(line) {
|
|
5619
6044
|
let inSingle = false;
|
|
5620
6045
|
let inDouble = false;
|
|
@@ -5676,10 +6101,10 @@ function sourceWeight(s) {
|
|
|
5676
6101
|
}
|
|
5677
6102
|
|
|
5678
6103
|
// src/git/GitExecutor.ts
|
|
5679
|
-
var
|
|
6104
|
+
var import_node_child_process10 = require("child_process");
|
|
5680
6105
|
var import_node_util2 = require("util");
|
|
5681
6106
|
var import_uuid7 = require("uuid");
|
|
5682
|
-
var execAsync2 = (0, import_node_util2.promisify)(
|
|
6107
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process10.exec);
|
|
5683
6108
|
var STATUS_TIMEOUT_MS = 15e3;
|
|
5684
6109
|
var COMMIT_TIMEOUT_MS = 6e4;
|
|
5685
6110
|
var PUSH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -5852,7 +6277,7 @@ var GitExecutor = class {
|
|
|
5852
6277
|
});
|
|
5853
6278
|
let proc;
|
|
5854
6279
|
try {
|
|
5855
|
-
proc = (0,
|
|
6280
|
+
proc = (0, import_node_child_process10.spawn)(cmd[0], cmd.slice(1), {
|
|
5856
6281
|
cwd: projectPath,
|
|
5857
6282
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5858
6283
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
@@ -6032,9 +6457,15 @@ function isValidTask(value) {
|
|
|
6032
6457
|
}
|
|
6033
6458
|
|
|
6034
6459
|
// src/utils/cliCapabilities.ts
|
|
6035
|
-
var
|
|
6460
|
+
var import_node_child_process11 = require("child_process");
|
|
6461
|
+
var DEFAULT_MODELS = [
|
|
6462
|
+
{ value: "opus", label: "Opus 4.7", sublabel: "Most capable for ambitious work" },
|
|
6463
|
+
{ value: "sonnet", label: "Sonnet 4.6", sublabel: "Most efficient for everyday tasks" },
|
|
6464
|
+
{ value: "haiku", label: "Haiku 4.5", sublabel: "Fastest for quick answers" }
|
|
6465
|
+
];
|
|
6036
6466
|
var DEFAULT_CAPABILITIES = {
|
|
6037
|
-
effortLevels: ["low", "medium", "high", "xhigh", "max"]
|
|
6467
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
6468
|
+
models: DEFAULT_MODELS
|
|
6038
6469
|
};
|
|
6039
6470
|
async function parseCliCapabilities() {
|
|
6040
6471
|
const claudePath = findClaudePath();
|
|
@@ -6060,7 +6491,7 @@ async function parseCliCapabilities() {
|
|
|
6060
6491
|
}
|
|
6061
6492
|
function runCli(path2, args) {
|
|
6062
6493
|
return new Promise((resolve) => {
|
|
6063
|
-
(0,
|
|
6494
|
+
(0, import_node_child_process11.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
|
|
6064
6495
|
if (err) {
|
|
6065
6496
|
console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
|
|
6066
6497
|
resolve(null);
|
|
@@ -6074,7 +6505,7 @@ function runCli(path2, args) {
|
|
|
6074
6505
|
// src/server.ts
|
|
6075
6506
|
var WS_PORT = 3745;
|
|
6076
6507
|
var HTTP_PORT = 3746;
|
|
6077
|
-
var execAsync3 = (0, import_node_util3.promisify)(
|
|
6508
|
+
var execAsync3 = (0, import_node_util3.promisify)(import_node_child_process12.exec);
|
|
6078
6509
|
async function killPortProcess(port) {
|
|
6079
6510
|
try {
|
|
6080
6511
|
if (isWindows) {
|
|
@@ -6102,6 +6533,39 @@ async function killPortProcess(port) {
|
|
|
6102
6533
|
} catch {
|
|
6103
6534
|
}
|
|
6104
6535
|
}
|
|
6536
|
+
async function loadApnsConfigFromFile() {
|
|
6537
|
+
const path2 = (0, import_node_path9.join)((0, import_node_os9.homedir)(), ".sessix", "apns.json");
|
|
6538
|
+
try {
|
|
6539
|
+
const raw = await (0, import_promises7.readFile)(path2, "utf8");
|
|
6540
|
+
const cfg = JSON.parse(raw);
|
|
6541
|
+
if (typeof cfg.teamId !== "string" || typeof cfg.keyId !== "string" || typeof cfg.authKeyPath !== "string") {
|
|
6542
|
+
console.warn(`[Server] \u26A0\uFE0F ${path2} \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5 (teamId / keyId / authKeyPath)\uFF0CLA \u540E\u53F0\u63A8\u9001\u5DF2\u7981\u7528`);
|
|
6543
|
+
return null;
|
|
6544
|
+
}
|
|
6545
|
+
try {
|
|
6546
|
+
await (0, import_promises7.readFile)(cfg.authKeyPath, "utf8");
|
|
6547
|
+
} catch (err) {
|
|
6548
|
+
console.warn(`[Server] \u26A0\uFE0F \u65E0\u6CD5\u8BFB\u53D6 APNs Auth Key: ${cfg.authKeyPath}`, err);
|
|
6549
|
+
return null;
|
|
6550
|
+
}
|
|
6551
|
+
console.log(`[Server] \u2705 \u5DF2\u52A0\u8F7D APNs \u914D\u7F6E (${path2})`);
|
|
6552
|
+
console.log(`[Server] teamId=${cfg.teamId} keyId=${cfg.keyId} sandbox=${cfg.sandbox === true}`);
|
|
6553
|
+
return {
|
|
6554
|
+
teamId: cfg.teamId,
|
|
6555
|
+
keyId: cfg.keyId,
|
|
6556
|
+
authKeyPath: cfg.authKeyPath,
|
|
6557
|
+
sandbox: cfg.sandbox === true
|
|
6558
|
+
};
|
|
6559
|
+
} catch (err) {
|
|
6560
|
+
const code = err.code;
|
|
6561
|
+
if (code === "ENOENT") {
|
|
6562
|
+
console.log(`[Server] \u2139\uFE0F ${path2} \u4E0D\u5B58\u5728\uFF0CLA \u540E\u53F0\u63A8\u9001\u672A\u542F\u7528\uFF08\u524D\u53F0 App \u4ECD\u80FD\u7528\u672C\u5730 Activity.update\uFF09`);
|
|
6563
|
+
} else {
|
|
6564
|
+
console.warn(`[Server] \u26A0\uFE0F \u8BFB\u53D6 ${path2} \u5931\u8D25:`, err);
|
|
6565
|
+
}
|
|
6566
|
+
return null;
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6105
6569
|
async function createWithRetry(label, port, factory) {
|
|
6106
6570
|
try {
|
|
6107
6571
|
return await factory();
|
|
@@ -6116,6 +6580,7 @@ async function createWithRetry(label, port, factory) {
|
|
|
6116
6580
|
}
|
|
6117
6581
|
}
|
|
6118
6582
|
async function start(opts = {}) {
|
|
6583
|
+
fixShellPath();
|
|
6119
6584
|
const configDir = (0, import_node_path9.join)((0, import_node_os9.homedir)(), ".sessix");
|
|
6120
6585
|
const tokenFile = (0, import_node_path9.join)(configDir, "token");
|
|
6121
6586
|
let token;
|
|
@@ -6164,9 +6629,10 @@ async function start(opts = {}) {
|
|
|
6164
6629
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
6165
6630
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
6166
6631
|
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
6167
|
-
|
|
6632
|
+
const activityPushOpts = opts.activityPush ?? await loadApnsConfigFromFile();
|
|
6633
|
+
if (activityPushOpts) {
|
|
6168
6634
|
try {
|
|
6169
|
-
const activityChannel = new ActivityPushChannel(
|
|
6635
|
+
const activityChannel = new ActivityPushChannel(activityPushOpts);
|
|
6170
6636
|
notificationService.setActivityPushChannel(activityChannel);
|
|
6171
6637
|
console.log(`[Server] ${t("server.activityPushEnabled")}`);
|
|
6172
6638
|
} catch (err) {
|
|
@@ -6201,6 +6667,9 @@ async function start(opts = {}) {
|
|
|
6201
6667
|
notificationService.setGlobalPendingCountProvider(
|
|
6202
6668
|
() => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
|
|
6203
6669
|
);
|
|
6670
|
+
notificationService.setPendingApprovalsProvider(
|
|
6671
|
+
(sessionId) => approvalProxy.getPendingRequestsForSession(sessionId)
|
|
6672
|
+
);
|
|
6204
6673
|
let cliCapabilities = null;
|
|
6205
6674
|
parseCliCapabilities().then((caps) => {
|
|
6206
6675
|
cliCapabilities = caps;
|
|
@@ -6271,7 +6740,7 @@ async function start(opts = {}) {
|
|
|
6271
6740
|
wsBridge.send(ws, { type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
|
|
6272
6741
|
}
|
|
6273
6742
|
if (cliCapabilities) {
|
|
6274
|
-
wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version });
|
|
6743
|
+
wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version, models: cliCapabilities.models });
|
|
6275
6744
|
}
|
|
6276
6745
|
if (scheduledManager) {
|
|
6277
6746
|
wsBridge.send(ws, { type: "scheduled_session_list", tasks: scheduledManager.list() });
|
|
@@ -6750,14 +7219,12 @@ async function start(opts = {}) {
|
|
|
6750
7219
|
setTimeout(() => {
|
|
6751
7220
|
if (!approvalProxy.isPending(request.id)) return;
|
|
6752
7221
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6753
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6754
7222
|
const pendingCount = approvalProxy.getPendingRequestsForSession(request.sessionId).length;
|
|
6755
7223
|
notificationService.notifyApproval(request, pendingCount);
|
|
6756
7224
|
}, 5e3);
|
|
6757
7225
|
setTimeout(() => {
|
|
6758
7226
|
if (!approvalProxy.isPending(request.id)) return;
|
|
6759
7227
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6760
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6761
7228
|
console.log(`[Server] ${t("server.approvalRetry", { id: request.id })}`);
|
|
6762
7229
|
const pendingCount = approvalProxy.getPendingRequestsForSession(request.sessionId).length;
|
|
6763
7230
|
notificationService.notifyApproval(request, pendingCount);
|
|
@@ -6769,13 +7236,11 @@ async function start(opts = {}) {
|
|
|
6769
7236
|
setTimeout(() => {
|
|
6770
7237
|
if (!sessionManager.isQuestionPending(request.id)) return;
|
|
6771
7238
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6772
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6773
7239
|
notificationService.notifyQuestion(request);
|
|
6774
7240
|
}, 5e3);
|
|
6775
7241
|
setTimeout(() => {
|
|
6776
7242
|
if (!sessionManager.isQuestionPending(request.id)) return;
|
|
6777
7243
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6778
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6779
7244
|
console.log(`[Server] Question ${request.id} not answered in 60s, retrying push`);
|
|
6780
7245
|
notificationService.notifyQuestion(request);
|
|
6781
7246
|
}, 6e4);
|
|
@@ -6964,7 +7429,7 @@ async function autoUpdateIfNeeded() {
|
|
|
6964
7429
|
console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
|
|
6965
7430
|
console.log();
|
|
6966
7431
|
try {
|
|
6967
|
-
(0,
|
|
7432
|
+
(0, import_node_child_process13.execFileSync)("npx", [`sessix-server@${latest}`], {
|
|
6968
7433
|
stdio: "inherit",
|
|
6969
7434
|
env: { ...process.env, __SESSIX_UPDATED: "1" }
|
|
6970
7435
|
});
|