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.
Files changed (3) hide show
  1. package/README.md +5 -2
  2. package/dist/cli.js +85 -4
  3. 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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weacpx",
3
3
  "description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
4
- "version": "0.1.4",
4
+ "version": "0.1.5",
5
5
  "main": "index.js",
6
6
  "directories": {
7
7
  "doc": "docs",