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/server.js
CHANGED
|
@@ -311,7 +311,7 @@ var import_uuid9 = require("uuid");
|
|
|
311
311
|
var import_promises7 = require("fs/promises");
|
|
312
312
|
var import_node_os9 = require("os");
|
|
313
313
|
var import_node_path9 = require("path");
|
|
314
|
-
var
|
|
314
|
+
var import_node_child_process12 = require("child_process");
|
|
315
315
|
var import_node_util3 = require("util");
|
|
316
316
|
|
|
317
317
|
// src/providers/ProcessProvider.ts
|
|
@@ -667,7 +667,24 @@ var ProcessProvider = class {
|
|
|
667
667
|
writeUserMessage(proc, message, sessionId, images) {
|
|
668
668
|
const content = [];
|
|
669
669
|
if (images?.length) {
|
|
670
|
-
|
|
670
|
+
const ALLOWED_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
|
|
671
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
672
|
+
for (let i = 0; i < images.length; i++) {
|
|
673
|
+
const img = images[i];
|
|
674
|
+
if (!ALLOWED_TYPES.has(img.media_type)) {
|
|
675
|
+
if (sessionId) {
|
|
676
|
+
this.emitWriteError(sessionId, `Image #${i + 1} rejected: unsupported media_type "${img.media_type}". Only JPEG/PNG/WebP/GIF are accepted.`);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const sizeBytes = Math.floor(img.data.length * 0.75);
|
|
681
|
+
if (sizeBytes > MAX_IMAGE_BYTES) {
|
|
682
|
+
if (sessionId) {
|
|
683
|
+
const sizeMb = (sizeBytes / (1024 * 1024)).toFixed(1);
|
|
684
|
+
this.emitWriteError(sessionId, `Image #${i + 1} rejected: ${sizeMb}MB exceeds 5MB per-image limit.`);
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
671
688
|
content.push({
|
|
672
689
|
type: "image",
|
|
673
690
|
source: { type: "base64", media_type: img.media_type, data: img.data }
|
|
@@ -694,6 +711,14 @@ var ProcessProvider = class {
|
|
|
694
711
|
this.emitWriteError(sessionId, `Failed to send message: ${err.message}`);
|
|
695
712
|
}
|
|
696
713
|
});
|
|
714
|
+
if (sessionId) {
|
|
715
|
+
const syntheticUser = {
|
|
716
|
+
type: "user",
|
|
717
|
+
session_id: sessionId,
|
|
718
|
+
message: { role: "user", content }
|
|
719
|
+
};
|
|
720
|
+
this.emitter.emit(this.getEventName(sessionId), syntheticUser);
|
|
721
|
+
}
|
|
697
722
|
}
|
|
698
723
|
/**
|
|
699
724
|
* 发出写入失败的合成错误事件
|
|
@@ -3715,6 +3740,8 @@ var HookInstaller = class {
|
|
|
3715
3740
|
|
|
3716
3741
|
// src/notification/NotificationService.ts
|
|
3717
3742
|
var import_node_path5 = require("path");
|
|
3743
|
+
var RECENT_ACTIVITY_MAX = 6;
|
|
3744
|
+
var ACTIVITY_PUSH_THROTTLE_MS = 2500;
|
|
3718
3745
|
var NotificationService = class {
|
|
3719
3746
|
constructor(sessionManager, expoChannel = null) {
|
|
3720
3747
|
this.sessionManager = sessionManager;
|
|
@@ -3733,6 +3760,14 @@ var NotificationService = class {
|
|
|
3733
3760
|
latestAssistantText = /* @__PURE__ */ new Map();
|
|
3734
3761
|
/** 获取全局待审批总数的回调(跨所有会话) */
|
|
3735
3762
|
globalPendingCountProvider = null;
|
|
3763
|
+
/** sessionId → 最近活动状态(用于 LA content push) */
|
|
3764
|
+
recentActivityState = /* @__PURE__ */ new Map();
|
|
3765
|
+
/** sessionId → 节流定时器(LA content push) */
|
|
3766
|
+
activityPushTimers = /* @__PURE__ */ new Map();
|
|
3767
|
+
/** 上次推送 LA content 的时间戳(用于节流;首次立即推送) */
|
|
3768
|
+
lastActivityPushAt = /* @__PURE__ */ new Map();
|
|
3769
|
+
/** 提供器:根据 sessionId 取该会话的待审批列表(由 server.ts 注入) */
|
|
3770
|
+
pendingApprovalsProvider = null;
|
|
3736
3771
|
/** 添加通知渠道(id 唯一,可用于后续动态开关) */
|
|
3737
3772
|
addChannel(id, channel, enabled = true) {
|
|
3738
3773
|
this.channelMap.set(id, { channel, enabled });
|
|
@@ -3760,11 +3795,24 @@ var NotificationService = class {
|
|
|
3760
3795
|
}
|
|
3761
3796
|
/** 注册 ActivityKit push token(由手机端启动 Live Activity 后上报) */
|
|
3762
3797
|
addActivityPushToken(sessionId, token) {
|
|
3763
|
-
this.activityPushChannel
|
|
3798
|
+
if (!this.activityPushChannel) {
|
|
3799
|
+
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`);
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
this.activityPushChannel.addToken(sessionId, token);
|
|
3803
|
+
console.log(`[NotificationService] \u2705 LA push token \u5DF2\u6CE8\u518C (session=${sessionId.slice(0, 8)}\u2026, token=${token.slice(0, 16)}\u2026)`);
|
|
3804
|
+
this.scheduleActivityPush(sessionId, true);
|
|
3764
3805
|
}
|
|
3765
3806
|
/** 移除 ActivityKit push token */
|
|
3766
3807
|
removeActivityPushToken(sessionId) {
|
|
3767
3808
|
this.activityPushChannel?.removeToken(sessionId);
|
|
3809
|
+
this.clearActivityPushTimer(sessionId);
|
|
3810
|
+
this.recentActivityState.delete(sessionId);
|
|
3811
|
+
this.lastActivityPushAt.delete(sessionId);
|
|
3812
|
+
}
|
|
3813
|
+
/** 注入"会话 → 待审批列表"提供器(server.ts 启动时调用) */
|
|
3814
|
+
setPendingApprovalsProvider(fn) {
|
|
3815
|
+
this.pendingApprovalsProvider = fn;
|
|
3768
3816
|
}
|
|
3769
3817
|
/** 设置全局待审批总数提供者 */
|
|
3770
3818
|
setGlobalPendingCountProvider(provider) {
|
|
@@ -3787,12 +3835,15 @@ var NotificationService = class {
|
|
|
3787
3835
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
3788
3836
|
const dangerLevel2 = this.getDangerLevel(request.toolName);
|
|
3789
3837
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
3838
|
+
const recentActivity = this.getRecentActivity(request.sessionId);
|
|
3839
|
+
const latestMessage = recentActivity[recentActivity.length - 1] ?? "";
|
|
3790
3840
|
this.activityPushChannel.updateActivityWithAlert(
|
|
3791
3841
|
request.sessionId,
|
|
3792
3842
|
{
|
|
3793
3843
|
status: "waitingApproval",
|
|
3794
3844
|
sessionTitle,
|
|
3795
|
-
latestMessage
|
|
3845
|
+
latestMessage,
|
|
3846
|
+
recentActivity,
|
|
3796
3847
|
approvalInfo: {
|
|
3797
3848
|
requestId: request.id,
|
|
3798
3849
|
toolName: request.toolName,
|
|
@@ -3805,6 +3856,7 @@ var NotificationService = class {
|
|
|
3805
3856
|
},
|
|
3806
3857
|
{ title, body }
|
|
3807
3858
|
);
|
|
3859
|
+
this.lastActivityPushAt.set(request.sessionId, Date.now());
|
|
3808
3860
|
return;
|
|
3809
3861
|
}
|
|
3810
3862
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
@@ -3838,17 +3890,20 @@ var NotificationService = class {
|
|
|
3838
3890
|
const body = `\u2753 ${request.question.slice(0, 80)}`;
|
|
3839
3891
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
3840
3892
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
3893
|
+
const recentActivity = this.getRecentActivity(request.sessionId);
|
|
3841
3894
|
this.activityPushChannel.updateActivityWithAlert(
|
|
3842
3895
|
request.sessionId,
|
|
3843
3896
|
{
|
|
3844
3897
|
status: "waitingApproval",
|
|
3845
3898
|
sessionTitle,
|
|
3846
3899
|
latestMessage: request.question.slice(0, 80),
|
|
3900
|
+
recentActivity,
|
|
3847
3901
|
isYoloMode,
|
|
3848
3902
|
updatedAt: Date.now()
|
|
3849
3903
|
},
|
|
3850
3904
|
{ title: sessionTitle, body }
|
|
3851
3905
|
);
|
|
3906
|
+
this.lastActivityPushAt.set(request.sessionId, Date.now());
|
|
3852
3907
|
return;
|
|
3853
3908
|
}
|
|
3854
3909
|
this.notify({
|
|
@@ -3882,6 +3937,10 @@ var NotificationService = class {
|
|
|
3882
3937
|
this.unsubscribe = null;
|
|
3883
3938
|
this.yoloModeState.clear();
|
|
3884
3939
|
this.latestAssistantText.clear();
|
|
3940
|
+
for (const timer of this.activityPushTimers.values()) clearTimeout(timer);
|
|
3941
|
+
this.activityPushTimers.clear();
|
|
3942
|
+
this.recentActivityState.clear();
|
|
3943
|
+
this.lastActivityPushAt.clear();
|
|
3885
3944
|
}
|
|
3886
3945
|
// ============================================
|
|
3887
3946
|
// 内部方法
|
|
@@ -3890,15 +3949,20 @@ var NotificationService = class {
|
|
|
3890
3949
|
switch (event.type) {
|
|
3891
3950
|
case "claude_event": {
|
|
3892
3951
|
this.trackAssistantText(event.sessionId, event.event);
|
|
3952
|
+
this.updateRecentActivity(event.sessionId, event.event);
|
|
3953
|
+
this.scheduleActivityPush(event.sessionId);
|
|
3893
3954
|
break;
|
|
3894
3955
|
}
|
|
3895
3956
|
case "claude_events": {
|
|
3896
3957
|
for (const e of event.events) {
|
|
3897
3958
|
this.trackAssistantText(event.sessionId, e);
|
|
3959
|
+
this.updateRecentActivity(event.sessionId, e);
|
|
3898
3960
|
}
|
|
3961
|
+
this.scheduleActivityPush(event.sessionId);
|
|
3899
3962
|
break;
|
|
3900
3963
|
}
|
|
3901
3964
|
case "status_change": {
|
|
3965
|
+
this.clearActivityPushTimer(event.sessionId);
|
|
3902
3966
|
if (event.status === "idle") {
|
|
3903
3967
|
const sessionTitle = this.getSessionTitle(event.sessionId);
|
|
3904
3968
|
const latestMsg = this.latestAssistantText.get(event.sessionId);
|
|
@@ -3909,9 +3973,12 @@ var NotificationService = class {
|
|
|
3909
3973
|
status: "idle",
|
|
3910
3974
|
sessionTitle,
|
|
3911
3975
|
latestMessage: body,
|
|
3976
|
+
recentActivity: this.getRecentActivity(event.sessionId),
|
|
3912
3977
|
isYoloMode,
|
|
3913
3978
|
updatedAt: Date.now()
|
|
3914
3979
|
});
|
|
3980
|
+
this.recentActivityState.delete(event.sessionId);
|
|
3981
|
+
this.lastActivityPushAt.delete(event.sessionId);
|
|
3915
3982
|
} else {
|
|
3916
3983
|
this.notify({
|
|
3917
3984
|
title: sessionTitle,
|
|
@@ -3931,9 +3998,12 @@ var NotificationService = class {
|
|
|
3931
3998
|
status: "error",
|
|
3932
3999
|
sessionTitle,
|
|
3933
4000
|
latestMessage: body,
|
|
4001
|
+
recentActivity: this.getRecentActivity(event.sessionId),
|
|
3934
4002
|
isYoloMode,
|
|
3935
4003
|
updatedAt: Date.now()
|
|
3936
4004
|
});
|
|
4005
|
+
this.recentActivityState.delete(event.sessionId);
|
|
4006
|
+
this.lastActivityPushAt.delete(event.sessionId);
|
|
3937
4007
|
} else {
|
|
3938
4008
|
this.notify({
|
|
3939
4009
|
title: sessionTitle,
|
|
@@ -3975,6 +4045,229 @@ var NotificationService = class {
|
|
|
3975
4045
|
getYoloMode(sessionId) {
|
|
3976
4046
|
return this.yoloModeState.get(sessionId) ?? false;
|
|
3977
4047
|
}
|
|
4048
|
+
// ============================================
|
|
4049
|
+
// Live Activity 内容推送(后台 LA 实时刷新)
|
|
4050
|
+
// ============================================
|
|
4051
|
+
/**
|
|
4052
|
+
* 把一个 ClaudeStreamEvent 折算到 recentActivity 列表里。
|
|
4053
|
+
* 同一 message.id 内多次 assistant 事件视为流式更新,整段重建 currentEntries;
|
|
4054
|
+
* 切换 message.id 视为新 turn,旧条目沉淀到 history。
|
|
4055
|
+
*/
|
|
4056
|
+
updateRecentActivity(sessionId, event) {
|
|
4057
|
+
if (event.type === "result") {
|
|
4058
|
+
const state2 = this.recentActivityState.get(sessionId);
|
|
4059
|
+
if (state2 && state2.currentEntries.length > 0) {
|
|
4060
|
+
state2.history.push(...state2.currentEntries);
|
|
4061
|
+
while (state2.history.length > RECENT_ACTIVITY_MAX) state2.history.shift();
|
|
4062
|
+
state2.currentEntries = [];
|
|
4063
|
+
state2.currentMessageId = null;
|
|
4064
|
+
}
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
if (event.type !== "assistant") return;
|
|
4068
|
+
const msg = event.message;
|
|
4069
|
+
if (!Array.isArray(msg.content)) return;
|
|
4070
|
+
let state = this.recentActivityState.get(sessionId);
|
|
4071
|
+
if (!state) {
|
|
4072
|
+
state = { history: [], currentMessageId: null, currentEntries: [] };
|
|
4073
|
+
this.recentActivityState.set(sessionId, state);
|
|
4074
|
+
}
|
|
4075
|
+
if (state.currentMessageId !== msg.id) {
|
|
4076
|
+
if (state.currentEntries.length > 0) {
|
|
4077
|
+
state.history.push(...state.currentEntries);
|
|
4078
|
+
while (state.history.length > RECENT_ACTIVITY_MAX) state.history.shift();
|
|
4079
|
+
}
|
|
4080
|
+
state.currentEntries = [];
|
|
4081
|
+
state.currentMessageId = msg.id;
|
|
4082
|
+
}
|
|
4083
|
+
const next = [];
|
|
4084
|
+
for (const block of msg.content) {
|
|
4085
|
+
if (block.type === "text") {
|
|
4086
|
+
const line = this.summarizeText(block.text);
|
|
4087
|
+
if (line.length >= 4) next.push(line);
|
|
4088
|
+
} else if (block.type === "tool_use") {
|
|
4089
|
+
const line = this.summarizeToolCall(block.name, block.input ?? {});
|
|
4090
|
+
if (line) next.push(line);
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
state.currentEntries = next;
|
|
4094
|
+
}
|
|
4095
|
+
/** 取该会话当前的 recentActivity(history + currentEntries),保留末尾 N 条 */
|
|
4096
|
+
getRecentActivity(sessionId) {
|
|
4097
|
+
const state = this.recentActivityState.get(sessionId);
|
|
4098
|
+
if (!state) return [];
|
|
4099
|
+
const combined = [...state.history, ...state.currentEntries];
|
|
4100
|
+
return combined.slice(-RECENT_ACTIVITY_MAX);
|
|
4101
|
+
}
|
|
4102
|
+
/** 节流调度 LA content push;首次立即推,后续合并到 throttle window 末尾 */
|
|
4103
|
+
scheduleActivityPush(sessionId, force = false) {
|
|
4104
|
+
if (!this.activityPushChannel?.hasToken(sessionId)) return;
|
|
4105
|
+
const now = Date.now();
|
|
4106
|
+
const last = this.lastActivityPushAt.get(sessionId) ?? 0;
|
|
4107
|
+
const elapsed = now - last;
|
|
4108
|
+
if (force || elapsed >= ACTIVITY_PUSH_THROTTLE_MS) {
|
|
4109
|
+
this.clearActivityPushTimer(sessionId);
|
|
4110
|
+
this.flushActivityPush(sessionId);
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
if (this.activityPushTimers.has(sessionId)) return;
|
|
4114
|
+
const wait = ACTIVITY_PUSH_THROTTLE_MS - elapsed;
|
|
4115
|
+
this.activityPushTimers.set(
|
|
4116
|
+
sessionId,
|
|
4117
|
+
setTimeout(() => {
|
|
4118
|
+
this.activityPushTimers.delete(sessionId);
|
|
4119
|
+
this.flushActivityPush(sessionId);
|
|
4120
|
+
}, wait)
|
|
4121
|
+
);
|
|
4122
|
+
}
|
|
4123
|
+
clearActivityPushTimer(sessionId) {
|
|
4124
|
+
const timer = this.activityPushTimers.get(sessionId);
|
|
4125
|
+
if (timer) {
|
|
4126
|
+
clearTimeout(timer);
|
|
4127
|
+
this.activityPushTimers.delete(sessionId);
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
/** 真正发送一次 LA content push(无 alert) */
|
|
4131
|
+
flushActivityPush(sessionId) {
|
|
4132
|
+
const channel = this.activityPushChannel;
|
|
4133
|
+
if (!channel?.hasToken(sessionId)) return;
|
|
4134
|
+
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
4135
|
+
if (!session) return;
|
|
4136
|
+
const recentActivity = this.getRecentActivity(sessionId);
|
|
4137
|
+
const latestMessage = recentActivity[recentActivity.length - 1] ?? this.latestAssistantText.get(sessionId) ?? "";
|
|
4138
|
+
const sessionTitle = this.getSessionTitle(sessionId);
|
|
4139
|
+
const isYoloMode = this.getYoloMode(sessionId);
|
|
4140
|
+
const pendingApprovals = this.pendingApprovalsProvider?.(sessionId) ?? [];
|
|
4141
|
+
const latestApproval = pendingApprovals[pendingApprovals.length - 1];
|
|
4142
|
+
const status = latestApproval ? "waitingApproval" : this.mapSessionStatus(session.status);
|
|
4143
|
+
const contentState = {
|
|
4144
|
+
status,
|
|
4145
|
+
sessionTitle,
|
|
4146
|
+
latestMessage,
|
|
4147
|
+
recentActivity,
|
|
4148
|
+
isYoloMode,
|
|
4149
|
+
updatedAt: Date.now()
|
|
4150
|
+
};
|
|
4151
|
+
if (latestApproval) {
|
|
4152
|
+
contentState.approvalInfo = {
|
|
4153
|
+
requestId: latestApproval.id,
|
|
4154
|
+
toolName: latestApproval.toolName,
|
|
4155
|
+
description: String(latestApproval.description ?? "").slice(0, 80),
|
|
4156
|
+
dangerLevel: this.getDangerLevel(latestApproval.toolName),
|
|
4157
|
+
pendingCount: pendingApprovals.length
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
if (session.stats) {
|
|
4161
|
+
contentState.stats = {
|
|
4162
|
+
totalInputTokens: session.stats.totalInputTokens,
|
|
4163
|
+
totalOutputTokens: session.stats.totalOutputTokens,
|
|
4164
|
+
totalCostUsd: session.stats.totalCostUsd
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
this.lastActivityPushAt.set(sessionId, Date.now());
|
|
4168
|
+
const lineCount = recentActivity.length;
|
|
4169
|
+
channel.updateActivity(sessionId, contentState).then(() => {
|
|
4170
|
+
console.log(`[NotificationService] \u{1F4E1} LA push \u2713 session=${sessionId.slice(0, 8)}\u2026 status=${status} lines=${lineCount}`);
|
|
4171
|
+
}).catch((err) => {
|
|
4172
|
+
console.warn(`[NotificationService] \u{1F4E1} LA push \u2717 session=${sessionId.slice(0, 8)}\u2026:`, err instanceof Error ? err.message : err);
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
/** SessionStatus → LiveActivity status 字符串映射(与客户端 mapStatus 一致) */
|
|
4176
|
+
mapSessionStatus(status) {
|
|
4177
|
+
switch (status) {
|
|
4178
|
+
case "running":
|
|
4179
|
+
return "running";
|
|
4180
|
+
case "waiting_approval":
|
|
4181
|
+
return "waitingApproval";
|
|
4182
|
+
case "waiting_question":
|
|
4183
|
+
return "waitingQuestion";
|
|
4184
|
+
case "idle":
|
|
4185
|
+
return "completed";
|
|
4186
|
+
case "completed":
|
|
4187
|
+
return "completed";
|
|
4188
|
+
case "error":
|
|
4189
|
+
return "error";
|
|
4190
|
+
default:
|
|
4191
|
+
return "idle";
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
/** 文本块清洗:去多余空白 + 截断到 70 字符 */
|
|
4195
|
+
summarizeText(raw) {
|
|
4196
|
+
if (typeof raw !== "string") return "";
|
|
4197
|
+
const cleaned = raw.replace(/\s+/g, " ").trim();
|
|
4198
|
+
return cleaned.length > 70 ? cleaned.slice(0, 70) + "\u2026" : cleaned;
|
|
4199
|
+
}
|
|
4200
|
+
/** 工具调用摘要(与客户端 summarizeToolCall 行为对齐,简化版只输出中文) */
|
|
4201
|
+
summarizeToolCall(name, input) {
|
|
4202
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
4203
|
+
const baseName = (p) => {
|
|
4204
|
+
const cleaned = p.split(/[?#]/)[0];
|
|
4205
|
+
const parts = cleaned.split("/");
|
|
4206
|
+
return parts[parts.length - 1] || cleaned;
|
|
4207
|
+
};
|
|
4208
|
+
const trunc = (s, n) => s.length > n ? s.slice(0, n) + "\u2026" : s;
|
|
4209
|
+
switch (name) {
|
|
4210
|
+
case "Bash": {
|
|
4211
|
+
const cmd = str(input.command).split("\n")[0];
|
|
4212
|
+
return cmd ? `\u8FD0\u884C: ${trunc(cmd, 60)}` : "\u6267\u884C\u547D\u4EE4";
|
|
4213
|
+
}
|
|
4214
|
+
case "Edit": {
|
|
4215
|
+
const fp = baseName(str(input.file_path));
|
|
4216
|
+
return fp ? `\u7F16\u8F91 ${fp}` : "\u7F16\u8F91\u6587\u4EF6";
|
|
4217
|
+
}
|
|
4218
|
+
case "MultiEdit": {
|
|
4219
|
+
const fp = baseName(str(input.file_path));
|
|
4220
|
+
return fp ? `\u6279\u91CF\u7F16\u8F91 ${fp}` : "\u6279\u91CF\u7F16\u8F91\u6587\u4EF6";
|
|
4221
|
+
}
|
|
4222
|
+
case "Write": {
|
|
4223
|
+
const fp = baseName(str(input.file_path));
|
|
4224
|
+
return fp ? `\u5199\u5165 ${fp}` : "\u5199\u5165\u6587\u4EF6";
|
|
4225
|
+
}
|
|
4226
|
+
case "Read":
|
|
4227
|
+
case "NotebookEdit": {
|
|
4228
|
+
const fp = baseName(str(input.file_path) || str(input.notebook_path));
|
|
4229
|
+
return fp ? `\u9605\u8BFB ${fp}` : "\u9605\u8BFB\u6587\u4EF6";
|
|
4230
|
+
}
|
|
4231
|
+
case "Grep": {
|
|
4232
|
+
const p = str(input.pattern);
|
|
4233
|
+
return p ? `\u641C\u7D22: ${trunc(p, 50)}` : "\u641C\u7D22\u4EE3\u7801";
|
|
4234
|
+
}
|
|
4235
|
+
case "Glob": {
|
|
4236
|
+
const p = str(input.pattern);
|
|
4237
|
+
return p ? `\u67E5\u627E: ${trunc(p, 50)}` : "\u67E5\u627E\u6587\u4EF6";
|
|
4238
|
+
}
|
|
4239
|
+
case "WebFetch": {
|
|
4240
|
+
const url = str(input.url);
|
|
4241
|
+
let host = url;
|
|
4242
|
+
try {
|
|
4243
|
+
host = new URL(url).hostname;
|
|
4244
|
+
} catch {
|
|
4245
|
+
}
|
|
4246
|
+
return host ? `\u8BF7\u6C42 ${trunc(host, 50)}` : "\u8BF7\u6C42\u7F51\u9875";
|
|
4247
|
+
}
|
|
4248
|
+
case "WebSearch": {
|
|
4249
|
+
const q = str(input.query);
|
|
4250
|
+
return q ? `\u641C\u7D22\u7F51\u9875: ${trunc(q, 50)}` : "\u641C\u7D22\u7F51\u9875";
|
|
4251
|
+
}
|
|
4252
|
+
case "TodoWrite":
|
|
4253
|
+
return "\u66F4\u65B0\u4EFB\u52A1\u6E05\u5355";
|
|
4254
|
+
case "Task":
|
|
4255
|
+
case "Agent": {
|
|
4256
|
+
const desc = str(input.description) || str(input.subagent_type);
|
|
4257
|
+
return desc ? `\u6D3E\u53D1\u4EFB\u52A1: ${trunc(desc, 50)}` : "\u6D3E\u53D1\u5B50\u4EFB\u52A1";
|
|
4258
|
+
}
|
|
4259
|
+
case "ExitPlanMode":
|
|
4260
|
+
return "\u63D0\u4EA4\u8BA1\u5212";
|
|
4261
|
+
case "Skill": {
|
|
4262
|
+
const skill = str(input.skill);
|
|
4263
|
+
return skill ? `\u8C03\u7528\u6280\u80FD: ${trunc(skill, 40)}` : "\u8C03\u7528\u6280\u80FD";
|
|
4264
|
+
}
|
|
4265
|
+
default: {
|
|
4266
|
+
const summary = trunc(JSON.stringify(input), 50);
|
|
4267
|
+
return name ? `${name}: ${summary}` : summary;
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
3978
4271
|
};
|
|
3979
4272
|
|
|
3980
4273
|
// src/notification/DesktopNotificationChannel.ts
|
|
@@ -4088,62 +4381,82 @@ var ExpoNotificationChannel = class {
|
|
|
4088
4381
|
var http2 = __toESM(require("http2"));
|
|
4089
4382
|
var fs2 = __toESM(require("fs"));
|
|
4090
4383
|
var crypto = __toESM(require("crypto"));
|
|
4384
|
+
var APNS_HOSTS = {
|
|
4385
|
+
production: "api.push.apple.com",
|
|
4386
|
+
sandbox: "api.sandbox.push.apple.com"
|
|
4387
|
+
};
|
|
4091
4388
|
var ActivityPushChannel = class {
|
|
4092
4389
|
/** sessionId -> activityPushToken */
|
|
4093
4390
|
tokens = /* @__PURE__ */ new Map();
|
|
4391
|
+
/**
|
|
4392
|
+
* 每个 token 已确认工作的 APNs 环境。
|
|
4393
|
+
* Debug build (aps-environment=development) 的 token 仅在 sandbox 端有效;
|
|
4394
|
+
* Release build (aps-environment=production) 的 token 仅在 production 端有效。
|
|
4395
|
+
* 同时维护两个连接 + 探测机制,避免环境配错时静默失败。
|
|
4396
|
+
*/
|
|
4397
|
+
tokenEnv = /* @__PURE__ */ new Map();
|
|
4398
|
+
/** 首次探测顺序(来自配置 hint),未配置则先试 sandbox(开发场景占多数) */
|
|
4399
|
+
probeOrder;
|
|
4094
4400
|
teamId;
|
|
4095
4401
|
keyId;
|
|
4096
4402
|
authKey;
|
|
4097
|
-
apnsHost;
|
|
4098
4403
|
/** 缓存的 JWT token + 过期时间 */
|
|
4099
4404
|
cachedJwt = null;
|
|
4100
|
-
/**
|
|
4101
|
-
|
|
4405
|
+
/** 每个环境一条 HTTP/2 长连接 */
|
|
4406
|
+
http2Clients = {};
|
|
4102
4407
|
constructor(config) {
|
|
4103
4408
|
this.teamId = config.teamId;
|
|
4104
4409
|
this.keyId = config.keyId;
|
|
4105
4410
|
this.authKey = fs2.readFileSync(config.authKeyPath, "utf-8");
|
|
4106
|
-
this.
|
|
4107
|
-
console.log(`[ActivityPushChannel] Initialized (${
|
|
4108
|
-
}
|
|
4109
|
-
/**
|
|
4110
|
-
getHttp2Client() {
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4411
|
+
this.probeOrder = config.sandbox === false ? ["production", "sandbox"] : ["sandbox", "production"];
|
|
4412
|
+
console.log(`[ActivityPushChannel] Initialized (probe order: ${this.probeOrder.join(" \u2192 ")})`);
|
|
4413
|
+
}
|
|
4414
|
+
/** 获取或新建指定环境的 HTTP/2 长连接 */
|
|
4415
|
+
getHttp2Client(env) {
|
|
4416
|
+
const existing = this.http2Clients[env];
|
|
4417
|
+
if (existing && !existing.destroyed && !existing.closed) {
|
|
4418
|
+
return existing;
|
|
4419
|
+
}
|
|
4420
|
+
const client = http2.connect(`https://${APNS_HOSTS[env]}`);
|
|
4421
|
+
client.on("error", (err) => {
|
|
4422
|
+
console.warn(`[ActivityPushChannel] HTTP/2 (${env}) error, will reconnect on next request:`, err.message);
|
|
4423
|
+
client.destroy();
|
|
4424
|
+
if (this.http2Clients[env] === client) delete this.http2Clients[env];
|
|
4119
4425
|
});
|
|
4120
|
-
|
|
4121
|
-
this.
|
|
4426
|
+
client.on("close", () => {
|
|
4427
|
+
if (this.http2Clients[env] === client) delete this.http2Clients[env];
|
|
4122
4428
|
});
|
|
4123
|
-
|
|
4429
|
+
this.http2Clients[env] = client;
|
|
4430
|
+
return client;
|
|
4124
4431
|
}
|
|
4125
4432
|
/** 注册 Activity push token */
|
|
4126
4433
|
addToken(sessionId, token) {
|
|
4434
|
+
const existed = this.tokens.has(sessionId);
|
|
4127
4435
|
this.tokens.set(sessionId, token);
|
|
4128
|
-
console.log(`[ActivityPushChannel] Token registered: session=${sessionId}`);
|
|
4436
|
+
console.log(`[ActivityPushChannel] Token ${existed ? "updated" : "registered"}: session=${sessionId.slice(0, 8)}\u2026 token=${token.slice(0, 16)}\u2026`);
|
|
4129
4437
|
}
|
|
4130
4438
|
/** 移除 Activity push token */
|
|
4131
4439
|
removeToken(sessionId) {
|
|
4440
|
+
const tok = this.tokens.get(sessionId);
|
|
4132
4441
|
this.tokens.delete(sessionId);
|
|
4442
|
+
if (tok) this.tokenEnv.delete(tok);
|
|
4133
4443
|
}
|
|
4134
|
-
/** 发送 content-state 更新到指定会话的 Live Activity */
|
|
4444
|
+
/** 发送 content-state 更新到指定会话的 Live Activity(纯内容刷新,不响通知) */
|
|
4135
4445
|
async updateActivity(sessionId, contentState) {
|
|
4136
4446
|
const token = this.tokens.get(sessionId);
|
|
4137
4447
|
if (!token) return;
|
|
4448
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4138
4449
|
const payload = {
|
|
4139
4450
|
aps: {
|
|
4140
|
-
timestamp:
|
|
4451
|
+
timestamp: now,
|
|
4141
4452
|
event: "update",
|
|
4142
|
-
"content-state": contentState
|
|
4453
|
+
"content-state": contentState,
|
|
4454
|
+
// 2 分钟没新内容就让 LA 进入 stale 状态(系统自动灰化),避免显示陈旧数据
|
|
4455
|
+
"stale-date": now + 120
|
|
4143
4456
|
}
|
|
4144
4457
|
};
|
|
4145
4458
|
try {
|
|
4146
|
-
await this.sendToAPNs(token, payload);
|
|
4459
|
+
await this.sendToAPNs(token, payload, { priority: "5" });
|
|
4147
4460
|
} catch (err) {
|
|
4148
4461
|
console.warn(`[ActivityPushChannel] Update failed session=${sessionId}:`, err);
|
|
4149
4462
|
}
|
|
@@ -4152,17 +4465,19 @@ var ActivityPushChannel = class {
|
|
|
4152
4465
|
async updateActivityWithAlert(sessionId, contentState, alert) {
|
|
4153
4466
|
const token = this.tokens.get(sessionId);
|
|
4154
4467
|
if (!token) return;
|
|
4468
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4155
4469
|
const payload = {
|
|
4156
4470
|
aps: {
|
|
4157
|
-
timestamp:
|
|
4471
|
+
timestamp: now,
|
|
4158
4472
|
event: "update",
|
|
4159
4473
|
"content-state": contentState,
|
|
4474
|
+
"stale-date": now + 120,
|
|
4160
4475
|
alert,
|
|
4161
4476
|
sound: "default"
|
|
4162
4477
|
}
|
|
4163
4478
|
};
|
|
4164
4479
|
try {
|
|
4165
|
-
await this.sendToAPNs(token, payload);
|
|
4480
|
+
await this.sendToAPNs(token, payload, { priority: "10" });
|
|
4166
4481
|
} catch (err) {
|
|
4167
4482
|
console.warn(`[ActivityPushChannel] Alert update failed session=${sessionId}:`, err);
|
|
4168
4483
|
}
|
|
@@ -4171,15 +4486,16 @@ var ActivityPushChannel = class {
|
|
|
4171
4486
|
async endActivity(sessionId, contentState) {
|
|
4172
4487
|
const token = this.tokens.get(sessionId);
|
|
4173
4488
|
if (!token) return;
|
|
4489
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4174
4490
|
const payload = {
|
|
4175
4491
|
aps: {
|
|
4176
|
-
timestamp:
|
|
4492
|
+
timestamp: now,
|
|
4177
4493
|
event: "end",
|
|
4178
4494
|
"content-state": contentState
|
|
4179
4495
|
}
|
|
4180
4496
|
};
|
|
4181
4497
|
try {
|
|
4182
|
-
await this.sendToAPNs(token, payload);
|
|
4498
|
+
await this.sendToAPNs(token, payload, { priority: "10" });
|
|
4183
4499
|
} catch (err) {
|
|
4184
4500
|
console.warn(`[ActivityPushChannel] End failed session=${sessionId}:`, err);
|
|
4185
4501
|
}
|
|
@@ -4189,15 +4505,44 @@ var ActivityPushChannel = class {
|
|
|
4189
4505
|
hasToken(sessionId) {
|
|
4190
4506
|
return this.tokens.has(sessionId);
|
|
4191
4507
|
}
|
|
4192
|
-
/**
|
|
4193
|
-
|
|
4508
|
+
/**
|
|
4509
|
+
* 发送 APNs,自动处理环境探测。
|
|
4510
|
+
* 对每个 token:先用已确认的环境(如有);否则按 probeOrder 顺序探测,
|
|
4511
|
+
* 收到 BadDeviceToken 自动切到另一个环境,并把成功的环境绑定到该 token。
|
|
4512
|
+
*/
|
|
4513
|
+
async sendToAPNs(deviceToken, payload, opts = {}) {
|
|
4514
|
+
const known = this.tokenEnv.get(deviceToken);
|
|
4515
|
+
if (known) {
|
|
4516
|
+
return this.sendToAPNsOnce(deviceToken, payload, opts, known);
|
|
4517
|
+
}
|
|
4518
|
+
let lastErr = null;
|
|
4519
|
+
for (const env of this.probeOrder) {
|
|
4520
|
+
try {
|
|
4521
|
+
await this.sendToAPNsOnce(deviceToken, payload, opts, env);
|
|
4522
|
+
this.tokenEnv.set(deviceToken, env);
|
|
4523
|
+
if (env !== this.probeOrder[0]) {
|
|
4524
|
+
console.log(`[ActivityPushChannel] Token bound to ${env} after probe (token=${deviceToken.slice(0, 16)}\u2026)`);
|
|
4525
|
+
}
|
|
4526
|
+
return;
|
|
4527
|
+
} catch (err) {
|
|
4528
|
+
lastErr = err;
|
|
4529
|
+
if (!isBadDeviceTokenError(err)) {
|
|
4530
|
+
throw err;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
throw lastErr ?? new Error("APNs send failed: all environments rejected token");
|
|
4535
|
+
}
|
|
4536
|
+
/** 单次 APNs HTTP/2 请求(内部 helper,不做探测) */
|
|
4537
|
+
async sendToAPNsOnce(deviceToken, payload, opts, env) {
|
|
4194
4538
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
4195
4539
|
const jwt = this.getJWT();
|
|
4196
4540
|
const payloadStr = JSON.stringify(payload);
|
|
4541
|
+
const priority = opts.priority ?? "10";
|
|
4197
4542
|
return new Promise((resolve, reject) => {
|
|
4198
4543
|
let client;
|
|
4199
4544
|
try {
|
|
4200
|
-
client = this.getHttp2Client();
|
|
4545
|
+
client = this.getHttp2Client(env);
|
|
4201
4546
|
} catch (err) {
|
|
4202
4547
|
return reject(err);
|
|
4203
4548
|
}
|
|
@@ -4207,7 +4552,7 @@ var ActivityPushChannel = class {
|
|
|
4207
4552
|
"authorization": `bearer ${jwt}`,
|
|
4208
4553
|
"apns-topic": topic,
|
|
4209
4554
|
"apns-push-type": "liveactivity",
|
|
4210
|
-
"apns-priority":
|
|
4555
|
+
"apns-priority": priority,
|
|
4211
4556
|
"apns-expiration": String(Math.floor(Date.now() / 1e3) + 30),
|
|
4212
4557
|
"content-type": "application/json",
|
|
4213
4558
|
"content-length": Buffer.byteLength(payloadStr)
|
|
@@ -4225,10 +4570,11 @@ var ActivityPushChannel = class {
|
|
|
4225
4570
|
resolve();
|
|
4226
4571
|
} else {
|
|
4227
4572
|
if (statusCode === 0) {
|
|
4228
|
-
this.
|
|
4229
|
-
|
|
4573
|
+
const c = this.http2Clients[env];
|
|
4574
|
+
c?.destroy();
|
|
4575
|
+
delete this.http2Clients[env];
|
|
4230
4576
|
}
|
|
4231
|
-
reject(new
|
|
4577
|
+
reject(new ApnsError(statusCode, responseData));
|
|
4232
4578
|
}
|
|
4233
4579
|
});
|
|
4234
4580
|
req.on("error", (err) => {
|
|
@@ -4261,6 +4607,24 @@ var ActivityPushChannel = class {
|
|
|
4261
4607
|
return token;
|
|
4262
4608
|
}
|
|
4263
4609
|
};
|
|
4610
|
+
var ApnsError = class extends Error {
|
|
4611
|
+
constructor(statusCode, responseBody) {
|
|
4612
|
+
super(`APNs returned ${statusCode}: ${responseBody}`);
|
|
4613
|
+
this.statusCode = statusCode;
|
|
4614
|
+
this.responseBody = responseBody;
|
|
4615
|
+
this.name = "ApnsError";
|
|
4616
|
+
}
|
|
4617
|
+
};
|
|
4618
|
+
function isBadDeviceTokenError(err) {
|
|
4619
|
+
if (!(err instanceof ApnsError)) return false;
|
|
4620
|
+
if (err.statusCode !== 400 && err.statusCode !== 410) return false;
|
|
4621
|
+
try {
|
|
4622
|
+
const parsed = JSON.parse(err.responseBody);
|
|
4623
|
+
return parsed.reason === "BadDeviceToken" || parsed.reason === "Unregistered";
|
|
4624
|
+
} catch {
|
|
4625
|
+
return false;
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4264
4628
|
|
|
4265
4629
|
// src/session/ProjectReader.ts
|
|
4266
4630
|
var import_promises3 = require("fs/promises");
|
|
@@ -4660,6 +5024,45 @@ var PairingManager = class {
|
|
|
4660
5024
|
}
|
|
4661
5025
|
};
|
|
4662
5026
|
|
|
5027
|
+
// src/utils/shellPath.ts
|
|
5028
|
+
var import_node_child_process7 = require("child_process");
|
|
5029
|
+
var fixed = false;
|
|
5030
|
+
function fixShellPath() {
|
|
5031
|
+
if (fixed || isWindows) {
|
|
5032
|
+
fixed = true;
|
|
5033
|
+
return;
|
|
5034
|
+
}
|
|
5035
|
+
fixed = true;
|
|
5036
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
5037
|
+
const isFish = /\/fish$/.test(shell);
|
|
5038
|
+
const printPathCmd = isFish ? "string join : $PATH" : 'printf "%s" "$PATH"';
|
|
5039
|
+
let raw;
|
|
5040
|
+
try {
|
|
5041
|
+
raw = (0, import_node_child_process7.execFileSync)(shell, ["-l", "-c", printPathCmd], {
|
|
5042
|
+
encoding: "utf8",
|
|
5043
|
+
timeout: 3e3,
|
|
5044
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5045
|
+
});
|
|
5046
|
+
} catch (err) {
|
|
5047
|
+
console.warn("[fixShellPath] failed to read login shell PATH:", err);
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5050
|
+
const fromShell = raw.trim();
|
|
5051
|
+
if (!fromShell) return;
|
|
5052
|
+
process.env.PATH = mergePath(fromShell, process.env.PATH || "");
|
|
5053
|
+
}
|
|
5054
|
+
function mergePath(primary, secondary) {
|
|
5055
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5056
|
+
const out = [];
|
|
5057
|
+
for (const seg of primary.split(":").concat(secondary.split(":"))) {
|
|
5058
|
+
if (!seg) continue;
|
|
5059
|
+
if (seen.has(seg)) continue;
|
|
5060
|
+
seen.add(seg);
|
|
5061
|
+
out.push(seg);
|
|
5062
|
+
}
|
|
5063
|
+
return out.join(":");
|
|
5064
|
+
}
|
|
5065
|
+
|
|
4663
5066
|
// src/auth/AuthManager.ts
|
|
4664
5067
|
var import_child_process3 = require("child_process");
|
|
4665
5068
|
var import_child_process4 = require("child_process");
|
|
@@ -4782,9 +5185,9 @@ var AuthManager = class extends import_events3.EventEmitter {
|
|
|
4782
5185
|
var import_promises8 = require("fs/promises");
|
|
4783
5186
|
|
|
4784
5187
|
// src/terminal/TerminalExecutor.ts
|
|
4785
|
-
var
|
|
5188
|
+
var import_node_child_process8 = require("child_process");
|
|
4786
5189
|
var import_uuid5 = require("uuid");
|
|
4787
|
-
var EXEC_TIMEOUT_MS =
|
|
5190
|
+
var EXEC_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4788
5191
|
var TerminalExecutor = class {
|
|
4789
5192
|
processes = /* @__PURE__ */ new Map();
|
|
4790
5193
|
eventCallbacks = [];
|
|
@@ -4806,9 +5209,9 @@ var TerminalExecutor = class {
|
|
|
4806
5209
|
}
|
|
4807
5210
|
exec(sessionId, command, cwd) {
|
|
4808
5211
|
const execId = (0, import_uuid5.v4)();
|
|
4809
|
-
const shell = isWindows ? "powershell" : "
|
|
4810
|
-
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
4811
|
-
const proc = (0,
|
|
5212
|
+
const shell = isWindows ? "powershell" : process.env.SHELL || "/bin/zsh";
|
|
5213
|
+
const args = isWindows ? ["-Command", command] : ["-l", "-c", command];
|
|
5214
|
+
const proc = (0, import_node_child_process8.spawn)(shell, args, {
|
|
4812
5215
|
cwd,
|
|
4813
5216
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4814
5217
|
env: { ...process.env }
|
|
@@ -4845,6 +5248,14 @@ var TerminalExecutor = class {
|
|
|
4845
5248
|
});
|
|
4846
5249
|
const timer = setTimeout(() => {
|
|
4847
5250
|
if (this.processes.has(execId)) {
|
|
5251
|
+
this.emit({
|
|
5252
|
+
type: "terminal_output",
|
|
5253
|
+
sessionId,
|
|
5254
|
+
execId,
|
|
5255
|
+
stream: "stderr",
|
|
5256
|
+
data: `[killed: timeout ${Math.round(EXEC_TIMEOUT_MS / 6e4)}m]
|
|
5257
|
+
`
|
|
5258
|
+
});
|
|
4848
5259
|
killProcessCrossPlatform(proc);
|
|
4849
5260
|
}
|
|
4850
5261
|
}, EXEC_TIMEOUT_MS);
|
|
@@ -4869,13 +5280,13 @@ var TerminalExecutor = class {
|
|
|
4869
5280
|
};
|
|
4870
5281
|
|
|
4871
5282
|
// src/xcode/XcodeBuildExecutor.ts
|
|
4872
|
-
var
|
|
5283
|
+
var import_node_child_process9 = require("child_process");
|
|
4873
5284
|
var import_node_util = require("util");
|
|
4874
5285
|
var import_promises4 = require("fs/promises");
|
|
4875
5286
|
var import_node_path6 = require("path");
|
|
4876
5287
|
var import_node_os7 = require("os");
|
|
4877
5288
|
var import_uuid6 = require("uuid");
|
|
4878
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
5289
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process9.exec);
|
|
4879
5290
|
var BUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4880
5291
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4881
5292
|
var CONFIG_FILE = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix", "xcode-config.json");
|
|
@@ -5064,7 +5475,7 @@ ${e.stderr ?? ""}`);
|
|
|
5064
5475
|
if (override) await this.saveConfig(projectPath, override);
|
|
5065
5476
|
const buildId = (0, import_uuid6.v4)();
|
|
5066
5477
|
const args = buildArgs(config);
|
|
5067
|
-
const proc = (0,
|
|
5478
|
+
const proc = (0, import_node_child_process9.spawn)("xcodebuild", args, {
|
|
5068
5479
|
cwd: projectPath,
|
|
5069
5480
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5070
5481
|
env: { ...process.env, NSUnbufferedIO: "YES" }
|
|
@@ -5160,7 +5571,7 @@ ${e.stderr ?? ""}`);
|
|
|
5160
5571
|
|
|
5161
5572
|
`
|
|
5162
5573
|
});
|
|
5163
|
-
const proc = (0,
|
|
5574
|
+
const proc = (0, import_node_child_process9.spawn)(installCmd[0], installCmd.slice(1), {
|
|
5164
5575
|
cwd: projectPath,
|
|
5165
5576
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5166
5577
|
});
|
|
@@ -5567,15 +5978,16 @@ var CommandDiscovery = class {
|
|
|
5567
5978
|
const cmd = sanitizeBashLine(rawLine);
|
|
5568
5979
|
if (!cmd) continue;
|
|
5569
5980
|
const { command: cleanCmd, inlineComment } = splitInlineComment(cmd);
|
|
5570
|
-
const
|
|
5981
|
+
const { cwd: cdCwd, command: finalCmd } = splitCdPrefix(cleanCmd);
|
|
5982
|
+
const title = synthesizeTitle(finalCmd);
|
|
5571
5983
|
out.push(makeCommand({
|
|
5572
5984
|
title,
|
|
5573
|
-
command:
|
|
5574
|
-
cwd:
|
|
5985
|
+
command: finalCmd,
|
|
5986
|
+
cwd: cdCwd,
|
|
5575
5987
|
source,
|
|
5576
5988
|
sourceFile: fileName,
|
|
5577
5989
|
description: inlineComment ?? blockHeading,
|
|
5578
|
-
category: classifyByCommand(
|
|
5990
|
+
category: classifyByCommand(finalCmd)
|
|
5579
5991
|
}));
|
|
5580
5992
|
}
|
|
5581
5993
|
}
|
|
@@ -5620,6 +6032,19 @@ function synthesizeTitle(cmd) {
|
|
|
5620
6032
|
const head = tokens.slice(0, 3).join(" ");
|
|
5621
6033
|
return head.length > 60 ? head.slice(0, 60) + "\u2026" : head;
|
|
5622
6034
|
}
|
|
6035
|
+
function splitCdPrefix(cmd) {
|
|
6036
|
+
const m = /^cd\s+(\S+)\s*&&\s*(.+)$/.exec(cmd);
|
|
6037
|
+
if (!m) return { cwd: "", command: cmd };
|
|
6038
|
+
const path2 = m[1];
|
|
6039
|
+
if (!path2) return { cwd: "", command: cmd };
|
|
6040
|
+
if (path2.startsWith("/") || path2.startsWith("~") || path2.startsWith("-")) {
|
|
6041
|
+
return { cwd: "", command: cmd };
|
|
6042
|
+
}
|
|
6043
|
+
if (path2.split("/").some((seg) => seg === "..")) {
|
|
6044
|
+
return { cwd: "", command: cmd };
|
|
6045
|
+
}
|
|
6046
|
+
return { cwd: path2, command: m[2].trim() };
|
|
6047
|
+
}
|
|
5623
6048
|
function splitInlineComment(line) {
|
|
5624
6049
|
let inSingle = false;
|
|
5625
6050
|
let inDouble = false;
|
|
@@ -5681,10 +6106,10 @@ function sourceWeight(s) {
|
|
|
5681
6106
|
}
|
|
5682
6107
|
|
|
5683
6108
|
// src/git/GitExecutor.ts
|
|
5684
|
-
var
|
|
6109
|
+
var import_node_child_process10 = require("child_process");
|
|
5685
6110
|
var import_node_util2 = require("util");
|
|
5686
6111
|
var import_uuid7 = require("uuid");
|
|
5687
|
-
var execAsync2 = (0, import_node_util2.promisify)(
|
|
6112
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process10.exec);
|
|
5688
6113
|
var STATUS_TIMEOUT_MS = 15e3;
|
|
5689
6114
|
var COMMIT_TIMEOUT_MS = 6e4;
|
|
5690
6115
|
var PUSH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -5857,7 +6282,7 @@ var GitExecutor = class {
|
|
|
5857
6282
|
});
|
|
5858
6283
|
let proc;
|
|
5859
6284
|
try {
|
|
5860
|
-
proc = (0,
|
|
6285
|
+
proc = (0, import_node_child_process10.spawn)(cmd[0], cmd.slice(1), {
|
|
5861
6286
|
cwd: projectPath,
|
|
5862
6287
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5863
6288
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
@@ -6037,9 +6462,15 @@ function isValidTask(value) {
|
|
|
6037
6462
|
}
|
|
6038
6463
|
|
|
6039
6464
|
// src/utils/cliCapabilities.ts
|
|
6040
|
-
var
|
|
6465
|
+
var import_node_child_process11 = require("child_process");
|
|
6466
|
+
var DEFAULT_MODELS = [
|
|
6467
|
+
{ value: "opus", label: "Opus 4.7", sublabel: "Most capable for ambitious work" },
|
|
6468
|
+
{ value: "sonnet", label: "Sonnet 4.6", sublabel: "Most efficient for everyday tasks" },
|
|
6469
|
+
{ value: "haiku", label: "Haiku 4.5", sublabel: "Fastest for quick answers" }
|
|
6470
|
+
];
|
|
6041
6471
|
var DEFAULT_CAPABILITIES = {
|
|
6042
|
-
effortLevels: ["low", "medium", "high", "xhigh", "max"]
|
|
6472
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
6473
|
+
models: DEFAULT_MODELS
|
|
6043
6474
|
};
|
|
6044
6475
|
async function parseCliCapabilities() {
|
|
6045
6476
|
const claudePath = findClaudePath();
|
|
@@ -6065,7 +6496,7 @@ async function parseCliCapabilities() {
|
|
|
6065
6496
|
}
|
|
6066
6497
|
function runCli(path2, args) {
|
|
6067
6498
|
return new Promise((resolve) => {
|
|
6068
|
-
(0,
|
|
6499
|
+
(0, import_node_child_process11.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
|
|
6069
6500
|
if (err) {
|
|
6070
6501
|
console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
|
|
6071
6502
|
resolve(null);
|
|
@@ -6079,7 +6510,7 @@ function runCli(path2, args) {
|
|
|
6079
6510
|
// src/server.ts
|
|
6080
6511
|
var WS_PORT = 3745;
|
|
6081
6512
|
var HTTP_PORT = 3746;
|
|
6082
|
-
var execAsync3 = (0, import_node_util3.promisify)(
|
|
6513
|
+
var execAsync3 = (0, import_node_util3.promisify)(import_node_child_process12.exec);
|
|
6083
6514
|
async function killPortProcess(port) {
|
|
6084
6515
|
try {
|
|
6085
6516
|
if (isWindows) {
|
|
@@ -6107,6 +6538,39 @@ async function killPortProcess(port) {
|
|
|
6107
6538
|
} catch {
|
|
6108
6539
|
}
|
|
6109
6540
|
}
|
|
6541
|
+
async function loadApnsConfigFromFile() {
|
|
6542
|
+
const path2 = (0, import_node_path9.join)((0, import_node_os9.homedir)(), ".sessix", "apns.json");
|
|
6543
|
+
try {
|
|
6544
|
+
const raw = await (0, import_promises7.readFile)(path2, "utf8");
|
|
6545
|
+
const cfg = JSON.parse(raw);
|
|
6546
|
+
if (typeof cfg.teamId !== "string" || typeof cfg.keyId !== "string" || typeof cfg.authKeyPath !== "string") {
|
|
6547
|
+
console.warn(`[Server] \u26A0\uFE0F ${path2} \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5 (teamId / keyId / authKeyPath)\uFF0CLA \u540E\u53F0\u63A8\u9001\u5DF2\u7981\u7528`);
|
|
6548
|
+
return null;
|
|
6549
|
+
}
|
|
6550
|
+
try {
|
|
6551
|
+
await (0, import_promises7.readFile)(cfg.authKeyPath, "utf8");
|
|
6552
|
+
} catch (err) {
|
|
6553
|
+
console.warn(`[Server] \u26A0\uFE0F \u65E0\u6CD5\u8BFB\u53D6 APNs Auth Key: ${cfg.authKeyPath}`, err);
|
|
6554
|
+
return null;
|
|
6555
|
+
}
|
|
6556
|
+
console.log(`[Server] \u2705 \u5DF2\u52A0\u8F7D APNs \u914D\u7F6E (${path2})`);
|
|
6557
|
+
console.log(`[Server] teamId=${cfg.teamId} keyId=${cfg.keyId} sandbox=${cfg.sandbox === true}`);
|
|
6558
|
+
return {
|
|
6559
|
+
teamId: cfg.teamId,
|
|
6560
|
+
keyId: cfg.keyId,
|
|
6561
|
+
authKeyPath: cfg.authKeyPath,
|
|
6562
|
+
sandbox: cfg.sandbox === true
|
|
6563
|
+
};
|
|
6564
|
+
} catch (err) {
|
|
6565
|
+
const code = err.code;
|
|
6566
|
+
if (code === "ENOENT") {
|
|
6567
|
+
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`);
|
|
6568
|
+
} else {
|
|
6569
|
+
console.warn(`[Server] \u26A0\uFE0F \u8BFB\u53D6 ${path2} \u5931\u8D25:`, err);
|
|
6570
|
+
}
|
|
6571
|
+
return null;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6110
6574
|
async function createWithRetry(label, port, factory) {
|
|
6111
6575
|
try {
|
|
6112
6576
|
return await factory();
|
|
@@ -6121,6 +6585,7 @@ async function createWithRetry(label, port, factory) {
|
|
|
6121
6585
|
}
|
|
6122
6586
|
}
|
|
6123
6587
|
async function start(opts = {}) {
|
|
6588
|
+
fixShellPath();
|
|
6124
6589
|
const configDir = (0, import_node_path9.join)((0, import_node_os9.homedir)(), ".sessix");
|
|
6125
6590
|
const tokenFile = (0, import_node_path9.join)(configDir, "token");
|
|
6126
6591
|
let token;
|
|
@@ -6169,9 +6634,10 @@ async function start(opts = {}) {
|
|
|
6169
6634
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
6170
6635
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
6171
6636
|
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
6172
|
-
|
|
6637
|
+
const activityPushOpts = opts.activityPush ?? await loadApnsConfigFromFile();
|
|
6638
|
+
if (activityPushOpts) {
|
|
6173
6639
|
try {
|
|
6174
|
-
const activityChannel = new ActivityPushChannel(
|
|
6640
|
+
const activityChannel = new ActivityPushChannel(activityPushOpts);
|
|
6175
6641
|
notificationService.setActivityPushChannel(activityChannel);
|
|
6176
6642
|
console.log(`[Server] ${t("server.activityPushEnabled")}`);
|
|
6177
6643
|
} catch (err) {
|
|
@@ -6206,6 +6672,9 @@ async function start(opts = {}) {
|
|
|
6206
6672
|
notificationService.setGlobalPendingCountProvider(
|
|
6207
6673
|
() => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
|
|
6208
6674
|
);
|
|
6675
|
+
notificationService.setPendingApprovalsProvider(
|
|
6676
|
+
(sessionId) => approvalProxy.getPendingRequestsForSession(sessionId)
|
|
6677
|
+
);
|
|
6209
6678
|
let cliCapabilities = null;
|
|
6210
6679
|
parseCliCapabilities().then((caps) => {
|
|
6211
6680
|
cliCapabilities = caps;
|
|
@@ -6276,7 +6745,7 @@ async function start(opts = {}) {
|
|
|
6276
6745
|
wsBridge.send(ws, { type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
|
|
6277
6746
|
}
|
|
6278
6747
|
if (cliCapabilities) {
|
|
6279
|
-
wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version });
|
|
6748
|
+
wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version, models: cliCapabilities.models });
|
|
6280
6749
|
}
|
|
6281
6750
|
if (scheduledManager) {
|
|
6282
6751
|
wsBridge.send(ws, { type: "scheduled_session_list", tasks: scheduledManager.list() });
|
|
@@ -6755,14 +7224,12 @@ async function start(opts = {}) {
|
|
|
6755
7224
|
setTimeout(() => {
|
|
6756
7225
|
if (!approvalProxy.isPending(request.id)) return;
|
|
6757
7226
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6758
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6759
7227
|
const pendingCount = approvalProxy.getPendingRequestsForSession(request.sessionId).length;
|
|
6760
7228
|
notificationService.notifyApproval(request, pendingCount);
|
|
6761
7229
|
}, 5e3);
|
|
6762
7230
|
setTimeout(() => {
|
|
6763
7231
|
if (!approvalProxy.isPending(request.id)) return;
|
|
6764
7232
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6765
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6766
7233
|
console.log(`[Server] ${t("server.approvalRetry", { id: request.id })}`);
|
|
6767
7234
|
const pendingCount = approvalProxy.getPendingRequestsForSession(request.sessionId).length;
|
|
6768
7235
|
notificationService.notifyApproval(request, pendingCount);
|
|
@@ -6774,13 +7241,11 @@ async function start(opts = {}) {
|
|
|
6774
7241
|
setTimeout(() => {
|
|
6775
7242
|
if (!sessionManager.isQuestionPending(request.id)) return;
|
|
6776
7243
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6777
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6778
7244
|
notificationService.notifyQuestion(request);
|
|
6779
7245
|
}, 5e3);
|
|
6780
7246
|
setTimeout(() => {
|
|
6781
7247
|
if (!sessionManager.isQuestionPending(request.id)) return;
|
|
6782
7248
|
if (wsBridge.isViewingSession(request.sessionId)) return;
|
|
6783
|
-
if (wsBridge.getConnectionCount() > 0) return;
|
|
6784
7249
|
console.log(`[Server] Question ${request.id} not answered in 60s, retrying push`);
|
|
6785
7250
|
notificationService.notifyQuestion(request);
|
|
6786
7251
|
}, 6e4);
|