weacpx 0.1.4 → 0.1.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/README.md +5 -2
- package/dist/cli.js +85 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,14 +155,17 @@ bun run dev
|
|
|
155
155
|
| `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
|
|
156
156
|
| `/use <alias>` | 切换当前会话 |
|
|
157
157
|
| `/status` | 查看当前会话状态 |
|
|
158
|
+
| `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
|
|
159
|
+
| `/clear` | `/session reset` 的快捷别名 |
|
|
158
160
|
| `/cancel` | 取消当前会话 |
|
|
159
|
-
| `/stop` |
|
|
161
|
+
| `/stop` | `/cancel` 的别名,用于取消当前会话 |
|
|
160
162
|
|
|
161
163
|
说明:
|
|
162
164
|
|
|
163
165
|
- `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
|
|
164
166
|
- `/ss new <agent> -d <path>` 表示强制新建 session
|
|
165
167
|
- `/use <alias>` 用来切换当前会话
|
|
168
|
+
- `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
|
|
166
169
|
- 非 `/` 开头的文本会发送到当前 session
|
|
167
170
|
|
|
168
171
|
### 权限策略
|
|
@@ -221,7 +224,7 @@ bun run dev
|
|
|
221
224
|
|
|
222
225
|
| 命令 | 说明 |
|
|
223
226
|
|------|------|
|
|
224
|
-
| `/clear` |
|
|
227
|
+
| `/clear` | 重置当前聊天绑定的会话上下文,效果等同于 `/session reset` |
|
|
225
228
|
| `/logout` | 清除当前机器上已保存的所有微信账号凭证 |
|
|
226
229
|
|
|
227
230
|
说明:
|
package/dist/cli.js
CHANGED
|
@@ -2777,7 +2777,7 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
2777
2777
|
return { handled: true };
|
|
2778
2778
|
}
|
|
2779
2779
|
case "/clear": {
|
|
2780
|
-
ctx.onClear?.();
|
|
2780
|
+
await ctx.onClear?.();
|
|
2781
2781
|
await sendReply(ctx, "✅ 会话已清除,重新开始对话");
|
|
2782
2782
|
return { handled: true };
|
|
2783
2783
|
}
|
|
@@ -3356,7 +3356,8 @@ function renderHelpText() {
|
|
|
3356
3356
|
"/pm auto [allow|deny|fail]",
|
|
3357
3357
|
"/use <alias>",
|
|
3358
3358
|
"/status",
|
|
3359
|
-
"/cancel 或 /stop"
|
|
3359
|
+
"/cancel 或 /stop",
|
|
3360
|
+
"/session reset 或 /clear"
|
|
3360
3361
|
].join(`
|
|
3361
3362
|
`);
|
|
3362
3363
|
}
|
|
@@ -3649,12 +3650,16 @@ function parseCommand(input) {
|
|
|
3649
3650
|
return { kind: "status" };
|
|
3650
3651
|
if (command === "/cancel")
|
|
3651
3652
|
return { kind: "cancel" };
|
|
3653
|
+
if (command === "/clear")
|
|
3654
|
+
return { kind: "session.reset" };
|
|
3652
3655
|
if (command === "/permission" && parts.length === 1)
|
|
3653
3656
|
return { kind: "permission.status" };
|
|
3654
3657
|
if (command === "/session" && parts.length === 1)
|
|
3655
3658
|
return { kind: "sessions" };
|
|
3656
3659
|
if (command === "/workspace" && parts.length === 1)
|
|
3657
3660
|
return { kind: "workspaces" };
|
|
3661
|
+
if (command === "/session" && parts[1] === "reset" && parts.length === 2)
|
|
3662
|
+
return { kind: "session.reset" };
|
|
3658
3663
|
if (command === "/permission" && parts[1] === "set") {
|
|
3659
3664
|
const mode = toPermissionMode(parts[2] ?? "");
|
|
3660
3665
|
if (mode) {
|
|
@@ -3714,7 +3719,7 @@ function parseCommand(input) {
|
|
|
3714
3719
|
return { kind: "session.shortcut.new", agent: parts[2], cwd };
|
|
3715
3720
|
}
|
|
3716
3721
|
}
|
|
3717
|
-
if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach") {
|
|
3722
|
+
if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach" && parts[1] !== "reset") {
|
|
3718
3723
|
const cwd = readFlagValue(parts, ["--cwd", "-d"]);
|
|
3719
3724
|
if (cwd) {
|
|
3720
3725
|
return { kind: "session.shortcut", agent: parts[1], cwd };
|
|
@@ -3824,6 +3829,7 @@ var init_parse_command = __esm(() => {
|
|
|
3824
3829
|
"/sessions",
|
|
3825
3830
|
"/status",
|
|
3826
3831
|
"/cancel",
|
|
3832
|
+
"/clear",
|
|
3827
3833
|
"/permission",
|
|
3828
3834
|
"/session",
|
|
3829
3835
|
"/workspace",
|
|
@@ -4038,6 +4044,8 @@ class CommandRouter {
|
|
|
4038
4044
|
return this.renderTransportError(session, error);
|
|
4039
4045
|
}
|
|
4040
4046
|
}
|
|
4047
|
+
case "session.reset":
|
|
4048
|
+
return await this.resetCurrentSession(chatKey);
|
|
4041
4049
|
case "prompt": {
|
|
4042
4050
|
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4043
4051
|
if (!session) {
|
|
@@ -4053,6 +4061,9 @@ class CommandRouter {
|
|
|
4053
4061
|
}
|
|
4054
4062
|
});
|
|
4055
4063
|
}
|
|
4064
|
+
async clearSession(chatKey) {
|
|
4065
|
+
await this.resetCurrentSession(chatKey);
|
|
4066
|
+
}
|
|
4056
4067
|
async handleSessionShortcut(chatKey, agent, cwdInput, createNew) {
|
|
4057
4068
|
if (!this.config || !this.configStore) {
|
|
4058
4069
|
return { text: "当前没有加载可写入的配置。" };
|
|
@@ -4241,6 +4252,41 @@ class CommandRouter {
|
|
|
4241
4252
|
async ensureTransportSession(session) {
|
|
4242
4253
|
await this.measureTransportCall("ensure_session", session, () => this.transport.ensureSession(session));
|
|
4243
4254
|
}
|
|
4255
|
+
async resetCurrentSession(chatKey) {
|
|
4256
|
+
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4257
|
+
if (!session) {
|
|
4258
|
+
return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
|
|
4259
|
+
}
|
|
4260
|
+
const resetSession = this.sessions.resolveSession(session.alias, session.agent, session.workspace, this.buildResetTransportSessionName(session));
|
|
4261
|
+
try {
|
|
4262
|
+
await this.ensureTransportSession(resetSession);
|
|
4263
|
+
const exists = await this.checkTransportSession(resetSession);
|
|
4264
|
+
if (!exists) {
|
|
4265
|
+
return {
|
|
4266
|
+
text: [
|
|
4267
|
+
`会话「${session.alias}」重置失败。`,
|
|
4268
|
+
"新的后端会话未创建成功,请稍后重试。"
|
|
4269
|
+
].join(`
|
|
4270
|
+
`)
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
} catch (error) {
|
|
4274
|
+
return this.renderTransportError(resetSession, error);
|
|
4275
|
+
}
|
|
4276
|
+
await this.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
|
|
4277
|
+
await this.sessions.useSession(chatKey, resetSession.alias);
|
|
4278
|
+
await this.logger.info("session.reset", "reset current logical session", {
|
|
4279
|
+
alias: resetSession.alias,
|
|
4280
|
+
agent: resetSession.agent,
|
|
4281
|
+
workspace: resetSession.workspace,
|
|
4282
|
+
transportSession: resetSession.transportSession,
|
|
4283
|
+
chatKey
|
|
4284
|
+
});
|
|
4285
|
+
return { text: `会话「${resetSession.alias}」已重置` };
|
|
4286
|
+
}
|
|
4287
|
+
buildResetTransportSessionName(session) {
|
|
4288
|
+
return `${session.workspace}:${session.alias}:reset-${Date.now()}`;
|
|
4289
|
+
}
|
|
4244
4290
|
async checkTransportSession(session) {
|
|
4245
4291
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
4246
4292
|
}
|
|
@@ -4644,6 +4690,9 @@ class ConsoleAgent {
|
|
|
4644
4690
|
});
|
|
4645
4691
|
return await this.router.handle(request.conversationId, request.text, request.reply);
|
|
4646
4692
|
}
|
|
4693
|
+
async clearSession(conversationId) {
|
|
4694
|
+
await this.router.clearSession?.(conversationId);
|
|
4695
|
+
}
|
|
4647
4696
|
}
|
|
4648
4697
|
function summarizeText(text) {
|
|
4649
4698
|
const trimmed = text.trim();
|
|
@@ -4803,7 +4852,15 @@ async function runConsole(paths, deps) {
|
|
|
4803
4852
|
const sdk = await deps.loadWeixinSdk();
|
|
4804
4853
|
const setIntervalFn = deps.setInterval ?? ((fn, delay) => setInterval(fn, delay));
|
|
4805
4854
|
const clearIntervalFn = deps.clearInterval ?? ((timer) => clearInterval(timer));
|
|
4855
|
+
const addProcessListener = deps.addProcessListener ?? ((signal, handler) => process.on(signal, handler));
|
|
4856
|
+
const removeProcessListener = deps.removeProcessListener ?? ((signal, handler) => process.off(signal, handler));
|
|
4806
4857
|
let heartbeatTimer = null;
|
|
4858
|
+
const shutdownController = new AbortController;
|
|
4859
|
+
const signalHandler = () => {
|
|
4860
|
+
shutdownController.abort();
|
|
4861
|
+
};
|
|
4862
|
+
addProcessListener("SIGINT", signalHandler);
|
|
4863
|
+
addProcessListener("SIGTERM", signalHandler);
|
|
4807
4864
|
try {
|
|
4808
4865
|
if (deps.daemonRuntime) {
|
|
4809
4866
|
await deps.daemonRuntime.start({
|
|
@@ -4818,9 +4875,11 @@ async function runConsole(paths, deps) {
|
|
|
4818
4875
|
console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
|
|
4819
4876
|
await sdk.login();
|
|
4820
4877
|
}
|
|
4821
|
-
await sdk.start(runtime.agent);
|
|
4878
|
+
await sdk.start(runtime.agent, { abortSignal: shutdownController.signal });
|
|
4822
4879
|
} finally {
|
|
4823
4880
|
let disposeError = null;
|
|
4881
|
+
removeProcessListener("SIGINT", signalHandler);
|
|
4882
|
+
removeProcessListener("SIGTERM", signalHandler);
|
|
4824
4883
|
if (heartbeatTimer !== null) {
|
|
4825
4884
|
clearIntervalFn(heartbeatTimer);
|
|
4826
4885
|
}
|
|
@@ -5521,15 +5580,23 @@ class DaemonController {
|
|
|
5521
5580
|
startupPollIntervalMs;
|
|
5522
5581
|
startupTimeoutMs;
|
|
5523
5582
|
onStartupPoll;
|
|
5583
|
+
shutdownPollIntervalMs;
|
|
5584
|
+
shutdownTimeoutMs;
|
|
5585
|
+
onShutdownPoll;
|
|
5524
5586
|
constructor(paths, deps) {
|
|
5525
5587
|
this.paths = paths;
|
|
5526
5588
|
this.deps = deps;
|
|
5527
5589
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
5528
5590
|
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
5529
5591
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
|
|
5592
|
+
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
5593
|
+
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
5530
5594
|
this.onStartupPoll = deps.onStartupPoll ?? (async () => {
|
|
5531
5595
|
await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
|
|
5532
5596
|
});
|
|
5597
|
+
this.onShutdownPoll = deps.onShutdownPoll ?? (async () => {
|
|
5598
|
+
await new Promise((resolve) => setTimeout(resolve, this.shutdownPollIntervalMs));
|
|
5599
|
+
});
|
|
5533
5600
|
}
|
|
5534
5601
|
async getStatus() {
|
|
5535
5602
|
const pid = await this.loadPid();
|
|
@@ -5568,6 +5635,7 @@ class DaemonController {
|
|
|
5568
5635
|
}
|
|
5569
5636
|
if (this.deps.isProcessRunning(pid)) {
|
|
5570
5637
|
await this.deps.terminateProcess(pid);
|
|
5638
|
+
await this.waitForShutdown(pid);
|
|
5571
5639
|
}
|
|
5572
5640
|
await this.clearRuntimeFiles();
|
|
5573
5641
|
return { state: "stopped", detail: "stopped" };
|
|
@@ -5608,6 +5676,19 @@ class DaemonController {
|
|
|
5608
5676
|
}
|
|
5609
5677
|
throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
|
|
5610
5678
|
}
|
|
5679
|
+
async waitForShutdown(pid) {
|
|
5680
|
+
const deadline = Date.now() + this.shutdownTimeoutMs;
|
|
5681
|
+
while (Date.now() < deadline) {
|
|
5682
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
5683
|
+
return;
|
|
5684
|
+
}
|
|
5685
|
+
await this.onShutdownPoll();
|
|
5686
|
+
}
|
|
5687
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
5688
|
+
return;
|
|
5689
|
+
}
|
|
5690
|
+
throw new Error(`weacpx daemon did not exit within ${this.shutdownTimeoutMs}ms (pid ${pid})`);
|
|
5691
|
+
}
|
|
5611
5692
|
}
|
|
5612
5693
|
|
|
5613
5694
|
// src/daemon/create-daemon-controller.ts
|