weacpx 0.1.4 → 0.1.6
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 +29 -2
- package/config.example.json +3 -1
- package/dist/bridge/bridge-main.js +41 -2
- package/dist/cli.js +320 -52
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -155,14 +155,21 @@ bun run dev
|
|
|
155
155
|
| `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
|
|
156
156
|
| `/use <alias>` | 切换当前会话 |
|
|
157
157
|
| `/status` | 查看当前会话状态 |
|
|
158
|
+
| `/mode` | 查看当前会话已保存的 mode |
|
|
159
|
+
| `/mode <id>` | 设置当前会话 mode,例如 `/mode plan` |
|
|
160
|
+
| `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
|
|
161
|
+
| `/clear` | `/session reset` 的快捷别名 |
|
|
158
162
|
| `/cancel` | 取消当前会话 |
|
|
159
|
-
| `/stop` |
|
|
163
|
+
| `/stop` | `/cancel` 的别名,用于取消当前会话 |
|
|
160
164
|
|
|
161
165
|
说明:
|
|
162
166
|
|
|
163
167
|
- `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
|
|
164
168
|
- `/ss new <agent> -d <path>` 表示强制新建 session
|
|
165
169
|
- `/use <alias>` 用来切换当前会话
|
|
170
|
+
- `/mode` 会显示当前逻辑会话里保存的 mode;如果还没设置过,会显示“未设置”
|
|
171
|
+
- `/mode <id>` 会把 mode 透传给底层 `acpx set-mode`,成功后再写回当前逻辑会话
|
|
172
|
+
- `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
|
|
166
173
|
- 非 `/` 开头的文本会发送到当前 session
|
|
167
174
|
|
|
168
175
|
### 权限策略
|
|
@@ -221,7 +228,7 @@ bun run dev
|
|
|
221
228
|
|
|
222
229
|
| 命令 | 说明 |
|
|
223
230
|
|------|------|
|
|
224
|
-
| `/clear` |
|
|
231
|
+
| `/clear` | 重置当前聊天绑定的会话上下文,效果等同于 `/session reset` |
|
|
225
232
|
| `/logout` | 清除当前机器上已保存的所有微信账号凭证 |
|
|
226
233
|
|
|
227
234
|
说明:
|
|
@@ -327,6 +334,26 @@ bun run dry-run --chat-key wx:test -- \
|
|
|
327
334
|
/ss attach demo -a codex --ws backend --name existing-demo
|
|
328
335
|
```
|
|
329
336
|
|
|
337
|
+
### Adapter mode 参考
|
|
338
|
+
|
|
339
|
+
`acpx set-mode` / 计划中的 `/mode <id>` 本质上都是给底层 ACP session 发送 `session/set_mode`。
|
|
340
|
+
这里的 `<id>` 不是 `weacpx` 或 `acpx` 统一规定的枚举,而是**各 adapter 自己定义**的值;填错时通常会收到 adapter 返回的 `Invalid params` 一类错误。
|
|
341
|
+
|
|
342
|
+
基于 `acpx` 内置 adapter 文档和各上游公开文档,当前能确认的信息如下:
|
|
343
|
+
|
|
344
|
+
| adapter | 已确认可用的 mode id | 说明 |
|
|
345
|
+
|------|------|------|
|
|
346
|
+
| `codex` | `plan` | `acpx` 自身示例明确使用过 `acpx codex set-mode plan`。`codex-acp` 还暴露了 `mode` 运行时配置项,但上游目前没有公开一份完整、稳定的 mode id 列表。 |
|
|
347
|
+
| `cursor` | `agent`、`plan`、`ask` | Cursor 官方文档/更新日志公开提到 `Plan mode`、`Ask mode`;Cursor 官方论坛在 ACP `session/configure` 示例中展示过 `availableModes` 为 `agent` / `plan` / `ask`。 |
|
|
348
|
+
| 其他内置 adapter | 暂无公开、稳定的 mode id 列表 | 包括 `claude`、`copilot`、`gemini`、`qoder`、`qwen`、`kimi`、`kiro`、`iflow`、`opencode`、`trae`、`droid`、`kilocode` 等。即使某些产品本身有“Ask / Agent / Plan”之类概念,其 ACP `set-mode` 可接受的精确字符串也往往没有在官方文档中写死。 |
|
|
349
|
+
|
|
350
|
+
建议:
|
|
351
|
+
|
|
352
|
+
- 对 `codex`,优先把 `plan` 当作已知可用值。
|
|
353
|
+
- 对 `cursor`,优先使用 `agent`、`plan`、`ask`。
|
|
354
|
+
- 对其他 adapter,不要在 `weacpx` 里写死候选值;最好把 `/mode <id>` 设计成透传,由 adapter 自己决定是否接受。
|
|
355
|
+
- 如果某个 adapter 后续补充了官方 mode 文档,再把它们补进这里。
|
|
356
|
+
|
|
330
357
|
## 更多文档
|
|
331
358
|
|
|
332
359
|
- 配置参考:[docs/config-reference.md](./docs/config-reference.md)
|
package/config.example.json
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -244,6 +262,14 @@ class BridgeServer {
|
|
|
244
262
|
name: String(params.name),
|
|
245
263
|
text: String(params.text)
|
|
246
264
|
});
|
|
265
|
+
case "setMode":
|
|
266
|
+
return await this.runtime.setMode({
|
|
267
|
+
agent: String(params.agent),
|
|
268
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
269
|
+
cwd: String(params.cwd),
|
|
270
|
+
name: String(params.name),
|
|
271
|
+
modeId: String(params.modeId)
|
|
272
|
+
});
|
|
247
273
|
case "cancel":
|
|
248
274
|
return await this.runtime.cancel({
|
|
249
275
|
agent: String(params.agent),
|
|
@@ -322,6 +348,19 @@ class BridgeRuntime {
|
|
|
322
348
|
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
323
349
|
return { text: getPromptText(result) };
|
|
324
350
|
}
|
|
351
|
+
async setMode(input) {
|
|
352
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
353
|
+
"set-mode",
|
|
354
|
+
"-s",
|
|
355
|
+
input.name,
|
|
356
|
+
input.modeId
|
|
357
|
+
]));
|
|
358
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
359
|
+
if (result.code !== 0) {
|
|
360
|
+
throw new Error(result.stderr || result.stdout || "set-mode failed");
|
|
361
|
+
}
|
|
362
|
+
return {};
|
|
363
|
+
}
|
|
325
364
|
async cancel(input) {
|
|
326
365
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
327
366
|
"cancel",
|
package/dist/cli.js
CHANGED
|
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
20
38
|
var __export = (target, all) => {
|
|
21
39
|
for (var name in all)
|
|
22
40
|
__defProp(target, name, {
|
|
23
41
|
get: all[name],
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
26
|
-
set: (
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
27
45
|
});
|
|
28
46
|
};
|
|
29
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -2777,7 +2795,7 @@ async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
|
2777
2795
|
return { handled: true };
|
|
2778
2796
|
}
|
|
2779
2797
|
case "/clear": {
|
|
2780
|
-
ctx.onClear?.();
|
|
2798
|
+
await ctx.onClear?.();
|
|
2781
2799
|
await sendReply(ctx, "✅ 会话已清除,重新开始对话");
|
|
2782
2800
|
return { handled: true };
|
|
2783
2801
|
}
|
|
@@ -3338,25 +3356,41 @@ var init_agent_templates = __esm(() => {
|
|
|
3338
3356
|
function renderHelpText() {
|
|
3339
3357
|
return [
|
|
3340
3358
|
"可用命令:",
|
|
3341
|
-
"
|
|
3342
|
-
"
|
|
3343
|
-
"/agent
|
|
3344
|
-
"/
|
|
3345
|
-
"/
|
|
3346
|
-
"
|
|
3347
|
-
"
|
|
3348
|
-
"/
|
|
3349
|
-
"/
|
|
3350
|
-
"/
|
|
3351
|
-
"
|
|
3352
|
-
"
|
|
3353
|
-
"/
|
|
3354
|
-
"/
|
|
3355
|
-
"/
|
|
3356
|
-
"/
|
|
3357
|
-
"
|
|
3358
|
-
"
|
|
3359
|
-
"/
|
|
3359
|
+
"",
|
|
3360
|
+
"先看这 3 个:",
|
|
3361
|
+
"/ss new <agent> -d <path> - 新建会话",
|
|
3362
|
+
"/use <alias> - 切会话",
|
|
3363
|
+
"/status - 看状态",
|
|
3364
|
+
"",
|
|
3365
|
+
"Agent:",
|
|
3366
|
+
"/agents - 看 Agent",
|
|
3367
|
+
"/agent add <codex|claude> - 加 Agent",
|
|
3368
|
+
"/agent rm <name> - 删 Agent",
|
|
3369
|
+
"",
|
|
3370
|
+
"工作区:",
|
|
3371
|
+
"/workspaces - 看工作区",
|
|
3372
|
+
"/workspace 或 /ws - 工作区命令",
|
|
3373
|
+
"/ws new <name> -d <path> - 加工作区",
|
|
3374
|
+
"/workspace rm <name> - 删工作区",
|
|
3375
|
+
"",
|
|
3376
|
+
"会话:",
|
|
3377
|
+
"/sessions - 看会话",
|
|
3378
|
+
"/session 或 /ss - 会话命令",
|
|
3379
|
+
"/ss <agent> -d <path> - 快速新建",
|
|
3380
|
+
"/ss new <agent> -d <path> - 新建会话",
|
|
3381
|
+
"/ss new <alias> -a <name> --ws <name> - 指定配置新建",
|
|
3382
|
+
"/ss attach <alias> -a <name> --ws <name> --name <transport-session> - 挂已有会话",
|
|
3383
|
+
"/use <alias> - 切会话",
|
|
3384
|
+
"/session reset 或 /clear - 清上下文",
|
|
3385
|
+
"",
|
|
3386
|
+
"权限:",
|
|
3387
|
+
"/pm 或 /permission - 权限设置",
|
|
3388
|
+
"/pm set <allow|read|deny> - 设审批级别",
|
|
3389
|
+
"/pm auto [allow|deny|fail] - 设自动处理",
|
|
3390
|
+
"",
|
|
3391
|
+
"常用:",
|
|
3392
|
+
"/status - 看状态",
|
|
3393
|
+
"/cancel 或 /stop - 停当前任务"
|
|
3360
3394
|
].join(`
|
|
3361
3395
|
`);
|
|
3362
3396
|
}
|
|
@@ -3497,6 +3531,27 @@ var init_app_logger = __esm(() => {
|
|
|
3497
3531
|
};
|
|
3498
3532
|
});
|
|
3499
3533
|
|
|
3534
|
+
// src/transport/acpx-session-index.ts
|
|
3535
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
3536
|
+
import { homedir } from "node:os";
|
|
3537
|
+
import { resolve } from "node:path";
|
|
3538
|
+
async function resolveSessionAgentCommandFromIndex(session) {
|
|
3539
|
+
const home = process.env.HOME ?? homedir();
|
|
3540
|
+
if (!home) {
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
try {
|
|
3544
|
+
const raw = await readFile3(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
3545
|
+
const parsed = JSON.parse(raw);
|
|
3546
|
+
const targetCwd = resolve(session.cwd);
|
|
3547
|
+
const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
3548
|
+
return match?.agentCommand?.trim();
|
|
3549
|
+
} catch {
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
var init_acpx_session_index = () => {};
|
|
3554
|
+
|
|
3500
3555
|
// src/transport/prompt-output.ts
|
|
3501
3556
|
function getPromptText(result) {
|
|
3502
3557
|
const stdoutOutput = extractPromptOutput(result.stdout);
|
|
@@ -3649,12 +3704,18 @@ function parseCommand(input) {
|
|
|
3649
3704
|
return { kind: "status" };
|
|
3650
3705
|
if (command === "/cancel")
|
|
3651
3706
|
return { kind: "cancel" };
|
|
3707
|
+
if (command === "/clear")
|
|
3708
|
+
return { kind: "session.reset" };
|
|
3709
|
+
if (command === "/mode" && parts.length === 1)
|
|
3710
|
+
return { kind: "mode.show" };
|
|
3652
3711
|
if (command === "/permission" && parts.length === 1)
|
|
3653
3712
|
return { kind: "permission.status" };
|
|
3654
3713
|
if (command === "/session" && parts.length === 1)
|
|
3655
3714
|
return { kind: "sessions" };
|
|
3656
3715
|
if (command === "/workspace" && parts.length === 1)
|
|
3657
3716
|
return { kind: "workspaces" };
|
|
3717
|
+
if (command === "/session" && parts[1] === "reset" && parts.length === 2)
|
|
3718
|
+
return { kind: "session.reset" };
|
|
3658
3719
|
if (command === "/permission" && parts[1] === "set") {
|
|
3659
3720
|
const mode = toPermissionMode(parts[2] ?? "");
|
|
3660
3721
|
if (mode) {
|
|
@@ -3673,6 +3734,9 @@ function parseCommand(input) {
|
|
|
3673
3734
|
if (command === "/use" && parts[1]) {
|
|
3674
3735
|
return { kind: "session.use", alias: parts[1] };
|
|
3675
3736
|
}
|
|
3737
|
+
if (command === "/mode" && parts[1]) {
|
|
3738
|
+
return { kind: "mode.set", modeId: parts[1] };
|
|
3739
|
+
}
|
|
3676
3740
|
if (command === "/agent" && parts[1] === "add" && parts[2]) {
|
|
3677
3741
|
return { kind: "agent.add", template: parts[2] };
|
|
3678
3742
|
}
|
|
@@ -3714,7 +3778,7 @@ function parseCommand(input) {
|
|
|
3714
3778
|
return { kind: "session.shortcut.new", agent: parts[2], cwd };
|
|
3715
3779
|
}
|
|
3716
3780
|
}
|
|
3717
|
-
if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach") {
|
|
3781
|
+
if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach" && parts[1] !== "reset") {
|
|
3718
3782
|
const cwd = readFlagValue(parts, ["--cwd", "-d"]);
|
|
3719
3783
|
if (cwd) {
|
|
3720
3784
|
return { kind: "session.shortcut", agent: parts[1], cwd };
|
|
@@ -3824,6 +3888,8 @@ var init_parse_command = __esm(() => {
|
|
|
3824
3888
|
"/sessions",
|
|
3825
3889
|
"/status",
|
|
3826
3890
|
"/cancel",
|
|
3891
|
+
"/clear",
|
|
3892
|
+
"/mode",
|
|
3827
3893
|
"/permission",
|
|
3828
3894
|
"/session",
|
|
3829
3895
|
"/workspace",
|
|
@@ -3835,19 +3901,21 @@ var init_parse_command = __esm(() => {
|
|
|
3835
3901
|
// src/commands/command-router.ts
|
|
3836
3902
|
import { access } from "node:fs/promises";
|
|
3837
3903
|
import { basename as basename2, normalize } from "node:path";
|
|
3838
|
-
import { homedir } from "node:os";
|
|
3904
|
+
import { homedir as homedir2 } from "node:os";
|
|
3839
3905
|
|
|
3840
3906
|
class CommandRouter {
|
|
3841
3907
|
sessions;
|
|
3842
3908
|
transport;
|
|
3843
3909
|
config;
|
|
3844
3910
|
configStore;
|
|
3911
|
+
resolveSessionAgentCommand;
|
|
3845
3912
|
logger;
|
|
3846
|
-
constructor(sessions, transport, config, configStore, logger2) {
|
|
3913
|
+
constructor(sessions, transport, config, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex) {
|
|
3847
3914
|
this.sessions = sessions;
|
|
3848
3915
|
this.transport = transport;
|
|
3849
3916
|
this.config = config;
|
|
3850
3917
|
this.configStore = configStore;
|
|
3918
|
+
this.resolveSessionAgentCommand = resolveSessionAgentCommand;
|
|
3851
3919
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
3852
3920
|
}
|
|
3853
3921
|
async handle(chatKey, input, reply) {
|
|
@@ -3970,6 +4038,7 @@ class CommandRouter {
|
|
|
3970
4038
|
return this.renderSessionCreationError(session, error);
|
|
3971
4039
|
}
|
|
3972
4040
|
await this.sessions.attachSession(command.alias, command.agent, command.workspace, session.transportSession);
|
|
4041
|
+
await this.refreshSessionTransportAgentCommand(command.alias);
|
|
3973
4042
|
await this.sessions.useSession(chatKey, command.alias);
|
|
3974
4043
|
await this.logger.info("session.created", "created and selected logical session", {
|
|
3975
4044
|
alias: command.alias,
|
|
@@ -3995,6 +4064,7 @@ class CommandRouter {
|
|
|
3995
4064
|
};
|
|
3996
4065
|
}
|
|
3997
4066
|
await this.sessions.attachSession(command.alias, command.agent, command.workspace, command.transportSession);
|
|
4067
|
+
await this.refreshSessionTransportAgentCommand(command.alias);
|
|
3998
4068
|
await this.sessions.useSession(chatKey, command.alias);
|
|
3999
4069
|
await this.logger.info("session.attached", "attached existing transport session", {
|
|
4000
4070
|
alias: command.alias,
|
|
@@ -4011,6 +4081,29 @@ class CommandRouter {
|
|
|
4011
4081
|
chatKey
|
|
4012
4082
|
});
|
|
4013
4083
|
return { text: `已切换到会话「${command.alias}」` };
|
|
4084
|
+
case "mode.show": {
|
|
4085
|
+
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4086
|
+
if (!session) {
|
|
4087
|
+
return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
|
|
4088
|
+
}
|
|
4089
|
+
return {
|
|
4090
|
+
text: [
|
|
4091
|
+
"当前 mode:",
|
|
4092
|
+
`- 会话:${session.alias}`,
|
|
4093
|
+
`- mode:${session.modeId ?? "未设置"}`
|
|
4094
|
+
].join(`
|
|
4095
|
+
`)
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
case "mode.set": {
|
|
4099
|
+
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4100
|
+
if (!session) {
|
|
4101
|
+
return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
|
|
4102
|
+
}
|
|
4103
|
+
await this.setModeTransportSession(session, command.modeId);
|
|
4104
|
+
await this.sessions.setCurrentSessionMode(chatKey, command.modeId);
|
|
4105
|
+
return { text: `已设置当前会话 mode:${command.modeId}` };
|
|
4106
|
+
}
|
|
4014
4107
|
case "status": {
|
|
4015
4108
|
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4016
4109
|
if (!session) {
|
|
@@ -4038,6 +4131,8 @@ class CommandRouter {
|
|
|
4038
4131
|
return this.renderTransportError(session, error);
|
|
4039
4132
|
}
|
|
4040
4133
|
}
|
|
4134
|
+
case "session.reset":
|
|
4135
|
+
return await this.resetCurrentSession(chatKey);
|
|
4041
4136
|
case "prompt": {
|
|
4042
4137
|
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4043
4138
|
if (!session) {
|
|
@@ -4047,12 +4142,20 @@ class CommandRouter {
|
|
|
4047
4142
|
const result = await this.promptTransportSession(session, command.text, reply);
|
|
4048
4143
|
return { text: result.text };
|
|
4049
4144
|
} catch (error) {
|
|
4145
|
+
const recovered = await this.tryRecoverMissingSession(session, error);
|
|
4146
|
+
if (recovered) {
|
|
4147
|
+
const result = await this.promptTransportSession(recovered, command.text, reply);
|
|
4148
|
+
return { text: result.text };
|
|
4149
|
+
}
|
|
4050
4150
|
return this.renderTransportError(session, error);
|
|
4051
4151
|
}
|
|
4052
4152
|
}
|
|
4053
4153
|
}
|
|
4054
4154
|
});
|
|
4055
4155
|
}
|
|
4156
|
+
async clearSession(chatKey) {
|
|
4157
|
+
await this.resetCurrentSession(chatKey);
|
|
4158
|
+
}
|
|
4056
4159
|
async handleSessionShortcut(chatKey, agent, cwdInput, createNew) {
|
|
4057
4160
|
if (!this.config || !this.configStore) {
|
|
4058
4161
|
return { text: "当前没有加载可写入的配置。" };
|
|
@@ -4096,6 +4199,7 @@ class CommandRouter {
|
|
|
4096
4199
|
return this.renderShortcutSessionCreationError(workspace, alias);
|
|
4097
4200
|
}
|
|
4098
4201
|
await this.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
|
|
4202
|
+
await this.refreshSessionTransportAgentCommand(alias);
|
|
4099
4203
|
await this.sessions.useSession(chatKey, alias);
|
|
4100
4204
|
await this.logger.info("session.shortcut.created", "created new logical session from shortcut", {
|
|
4101
4205
|
alias,
|
|
@@ -4241,15 +4345,77 @@ class CommandRouter {
|
|
|
4241
4345
|
async ensureTransportSession(session) {
|
|
4242
4346
|
await this.measureTransportCall("ensure_session", session, () => this.transport.ensureSession(session));
|
|
4243
4347
|
}
|
|
4348
|
+
async resetCurrentSession(chatKey) {
|
|
4349
|
+
const session = await this.sessions.getCurrentSession(chatKey);
|
|
4350
|
+
if (!session) {
|
|
4351
|
+
return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
|
|
4352
|
+
}
|
|
4353
|
+
const resetSession = this.sessions.resolveSession(session.alias, session.agent, session.workspace, this.buildResetTransportSessionName(session));
|
|
4354
|
+
try {
|
|
4355
|
+
await this.ensureTransportSession(resetSession);
|
|
4356
|
+
const exists = await this.checkTransportSession(resetSession);
|
|
4357
|
+
if (!exists) {
|
|
4358
|
+
return {
|
|
4359
|
+
text: [
|
|
4360
|
+
`会话「${session.alias}」重置失败。`,
|
|
4361
|
+
"新的后端会话未创建成功,请稍后重试。"
|
|
4362
|
+
].join(`
|
|
4363
|
+
`)
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
} catch (error) {
|
|
4367
|
+
return this.renderTransportError(resetSession, error);
|
|
4368
|
+
}
|
|
4369
|
+
await this.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
|
|
4370
|
+
await this.refreshSessionTransportAgentCommand(resetSession.alias);
|
|
4371
|
+
await this.sessions.useSession(chatKey, resetSession.alias);
|
|
4372
|
+
await this.logger.info("session.reset", "reset current logical session", {
|
|
4373
|
+
alias: resetSession.alias,
|
|
4374
|
+
agent: resetSession.agent,
|
|
4375
|
+
workspace: resetSession.workspace,
|
|
4376
|
+
transportSession: resetSession.transportSession,
|
|
4377
|
+
chatKey
|
|
4378
|
+
});
|
|
4379
|
+
return { text: `会话「${resetSession.alias}」已重置` };
|
|
4380
|
+
}
|
|
4381
|
+
buildResetTransportSessionName(session) {
|
|
4382
|
+
return `${session.workspace}:${session.alias}:reset-${Date.now()}`;
|
|
4383
|
+
}
|
|
4244
4384
|
async checkTransportSession(session) {
|
|
4245
4385
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
4246
4386
|
}
|
|
4247
4387
|
async promptTransportSession(session, text, reply) {
|
|
4248
4388
|
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply));
|
|
4249
4389
|
}
|
|
4390
|
+
async setModeTransportSession(session, modeId) {
|
|
4391
|
+
return await this.measureTransportCall("set_mode", session, () => this.transport.setMode(session, modeId));
|
|
4392
|
+
}
|
|
4250
4393
|
async cancelTransportSession(session) {
|
|
4251
4394
|
return await this.measureTransportCall("cancel", session, () => this.transport.cancel(session));
|
|
4252
4395
|
}
|
|
4396
|
+
async refreshSessionTransportAgentCommand(alias) {
|
|
4397
|
+
const session = await this.sessions.getSession(alias);
|
|
4398
|
+
if (!session) {
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
const transportAgentCommand = await this.resolveSessionAgentCommand(session);
|
|
4402
|
+
if (!transportAgentCommand) {
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
await this.sessions.setSessionTransportAgentCommand(alias, transportAgentCommand);
|
|
4406
|
+
}
|
|
4407
|
+
async tryRecoverMissingSession(session, error) {
|
|
4408
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4409
|
+
if (!message.includes("No acpx session found")) {
|
|
4410
|
+
return null;
|
|
4411
|
+
}
|
|
4412
|
+
const transportAgentCommand = await this.resolveSessionAgentCommand(session);
|
|
4413
|
+
if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
|
|
4414
|
+
return null;
|
|
4415
|
+
}
|
|
4416
|
+
await this.sessions.setSessionTransportAgentCommand(session.alias, transportAgentCommand);
|
|
4417
|
+
return await this.sessions.getSession(session.alias);
|
|
4418
|
+
}
|
|
4253
4419
|
async measureTransportCall(operation, session, callback) {
|
|
4254
4420
|
const startedAt = Date.now();
|
|
4255
4421
|
try {
|
|
@@ -4296,7 +4462,7 @@ async function pathExists(path11) {
|
|
|
4296
4462
|
}
|
|
4297
4463
|
}
|
|
4298
4464
|
function normalizePathForWorkspace(path11) {
|
|
4299
|
-
const expanded = path11.startsWith("~") ?
|
|
4465
|
+
const expanded = path11.startsWith("~") ? homedir2() + path11.slice(1) : path11;
|
|
4300
4466
|
return normalize(expanded);
|
|
4301
4467
|
}
|
|
4302
4468
|
function sameWorkspacePath(left, right) {
|
|
@@ -4369,6 +4535,7 @@ function isPartialPromptOutputError(message) {
|
|
|
4369
4535
|
var init_command_router = __esm(() => {
|
|
4370
4536
|
init_agent_templates();
|
|
4371
4537
|
init_app_logger();
|
|
4538
|
+
init_acpx_session_index();
|
|
4372
4539
|
init_prompt_output();
|
|
4373
4540
|
init_parse_command();
|
|
4374
4541
|
});
|
|
@@ -4389,12 +4556,12 @@ function isLegacyCodexCommand(command) {
|
|
|
4389
4556
|
}
|
|
4390
4557
|
|
|
4391
4558
|
// src/config/load-config.ts
|
|
4392
|
-
import { readFile as
|
|
4559
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
4393
4560
|
function isRecord(value) {
|
|
4394
4561
|
return typeof value === "object" && value !== null;
|
|
4395
4562
|
}
|
|
4396
4563
|
async function loadConfig(path11, options = {}) {
|
|
4397
|
-
const raw = JSON.parse(await
|
|
4564
|
+
const raw = JSON.parse(await readFile4(path11, "utf8"));
|
|
4398
4565
|
return parseConfig(raw, options);
|
|
4399
4566
|
}
|
|
4400
4567
|
function parseConfig(raw, options = {}) {
|
|
@@ -4562,7 +4729,7 @@ var init_config_store = __esm(() => {
|
|
|
4562
4729
|
});
|
|
4563
4730
|
|
|
4564
4731
|
// src/config/ensure-config.ts
|
|
4565
|
-
import { readFile as
|
|
4732
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
4566
4733
|
async function ensureConfigExists(path11) {
|
|
4567
4734
|
try {
|
|
4568
4735
|
await loadConfig(path11);
|
|
@@ -4576,7 +4743,7 @@ async function ensureConfigExists(path11) {
|
|
|
4576
4743
|
}
|
|
4577
4744
|
async function loadDefaultConfigTemplate() {
|
|
4578
4745
|
const templatePath = new URL("../../config.example.json", import.meta.url);
|
|
4579
|
-
const template = JSON.parse(await
|
|
4746
|
+
const template = JSON.parse(await readFile5(templatePath, "utf8"));
|
|
4580
4747
|
return {
|
|
4581
4748
|
...template,
|
|
4582
4749
|
agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
|
|
@@ -4644,6 +4811,9 @@ class ConsoleAgent {
|
|
|
4644
4811
|
});
|
|
4645
4812
|
return await this.router.handle(request.conversationId, request.text, request.reply);
|
|
4646
4813
|
}
|
|
4814
|
+
async clearSession(conversationId) {
|
|
4815
|
+
await this.router.clearSession?.(conversationId);
|
|
4816
|
+
}
|
|
4647
4817
|
}
|
|
4648
4818
|
function summarizeText(text) {
|
|
4649
4819
|
const trimmed = text.trim();
|
|
@@ -4676,12 +4846,20 @@ class SessionService {
|
|
|
4676
4846
|
agent,
|
|
4677
4847
|
workspace,
|
|
4678
4848
|
transport_session: transportSession,
|
|
4849
|
+
transport_agent_command: this.state.sessions[alias]?.transport_agent_command,
|
|
4679
4850
|
created_at: this.state.sessions[alias]?.created_at ?? new Date().toISOString(),
|
|
4680
4851
|
last_used_at: new Date().toISOString()
|
|
4681
4852
|
});
|
|
4682
4853
|
}
|
|
4683
|
-
async attachSession(alias, agent, workspace, transportSession) {
|
|
4684
|
-
return await this.createLogicalSession(alias, agent, workspace, transportSession);
|
|
4854
|
+
async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
|
|
4855
|
+
return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
|
|
4856
|
+
}
|
|
4857
|
+
async getSession(alias) {
|
|
4858
|
+
const session = this.state.sessions[alias];
|
|
4859
|
+
if (!session) {
|
|
4860
|
+
return null;
|
|
4861
|
+
}
|
|
4862
|
+
return this.toResolvedSession(session);
|
|
4685
4863
|
}
|
|
4686
4864
|
async useSession(chatKey, alias) {
|
|
4687
4865
|
const session = this.state.sessions[alias];
|
|
@@ -4692,6 +4870,24 @@ class SessionService {
|
|
|
4692
4870
|
this.state.chat_contexts[chatKey] = { current_session: alias };
|
|
4693
4871
|
await this.persist();
|
|
4694
4872
|
}
|
|
4873
|
+
async setCurrentSessionMode(chatKey, modeId) {
|
|
4874
|
+
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
4875
|
+
if (!currentAlias) {
|
|
4876
|
+
throw new Error("no current session selected");
|
|
4877
|
+
}
|
|
4878
|
+
const session = this.state.sessions[currentAlias];
|
|
4879
|
+
if (!session) {
|
|
4880
|
+
throw new Error("no current session selected");
|
|
4881
|
+
}
|
|
4882
|
+
const normalizedModeId = modeId?.trim();
|
|
4883
|
+
if (normalizedModeId) {
|
|
4884
|
+
session.mode_id = normalizedModeId;
|
|
4885
|
+
} else {
|
|
4886
|
+
delete session.mode_id;
|
|
4887
|
+
}
|
|
4888
|
+
session.last_used_at = new Date().toISOString();
|
|
4889
|
+
await this.persist();
|
|
4890
|
+
}
|
|
4695
4891
|
async getCurrentSession(chatKey) {
|
|
4696
4892
|
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
4697
4893
|
if (!currentAlias) {
|
|
@@ -4719,24 +4915,42 @@ class SessionService {
|
|
|
4719
4915
|
return {
|
|
4720
4916
|
alias: session.alias,
|
|
4721
4917
|
agent: session.agent,
|
|
4722
|
-
agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
|
|
4918
|
+
agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
|
|
4723
4919
|
workspace: session.workspace,
|
|
4724
4920
|
transportSession: session.transport_session,
|
|
4921
|
+
modeId: session.mode_id,
|
|
4725
4922
|
cwd: this.config.workspaces[session.workspace].cwd
|
|
4726
4923
|
};
|
|
4727
4924
|
}
|
|
4925
|
+
async setSessionTransportAgentCommand(alias, transportAgentCommand) {
|
|
4926
|
+
const session = this.state.sessions[alias];
|
|
4927
|
+
if (!session) {
|
|
4928
|
+
throw new Error(`session "${alias}" does not exist`);
|
|
4929
|
+
}
|
|
4930
|
+
const normalized = transportAgentCommand?.trim();
|
|
4931
|
+
if (normalized) {
|
|
4932
|
+
session.transport_agent_command = normalized;
|
|
4933
|
+
} else {
|
|
4934
|
+
delete session.transport_agent_command;
|
|
4935
|
+
}
|
|
4936
|
+
session.last_used_at = new Date().toISOString();
|
|
4937
|
+
await this.persist();
|
|
4938
|
+
}
|
|
4728
4939
|
async persist() {
|
|
4729
4940
|
await this.stateStore.save(this.state);
|
|
4730
4941
|
}
|
|
4731
|
-
async createLogicalSession(alias, agent, workspace, transportSession) {
|
|
4942
|
+
async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
|
|
4732
4943
|
this.validateSession(alias, agent, workspace);
|
|
4733
4944
|
const existingSession = this.state.sessions[alias];
|
|
4734
4945
|
const now = new Date().toISOString();
|
|
4946
|
+
const normalizedTransportAgentCommand = transportAgentCommand?.trim();
|
|
4735
4947
|
const session = {
|
|
4736
4948
|
alias,
|
|
4737
4949
|
agent,
|
|
4738
4950
|
workspace,
|
|
4739
4951
|
transport_session: transportSession,
|
|
4952
|
+
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
|
|
4953
|
+
mode_id: existingSession?.mode_id,
|
|
4740
4954
|
created_at: existingSession?.created_at ?? now,
|
|
4741
4955
|
last_used_at: now
|
|
4742
4956
|
};
|
|
@@ -4764,7 +4978,7 @@ function createEmptyState() {
|
|
|
4764
4978
|
}
|
|
4765
4979
|
|
|
4766
4980
|
// src/state/state-store.ts
|
|
4767
|
-
import { mkdir as mkdir7, readFile as
|
|
4981
|
+
import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
4768
4982
|
import { dirname as dirname6 } from "node:path";
|
|
4769
4983
|
|
|
4770
4984
|
class StateStore {
|
|
@@ -4774,7 +4988,7 @@ class StateStore {
|
|
|
4774
4988
|
}
|
|
4775
4989
|
async load() {
|
|
4776
4990
|
try {
|
|
4777
|
-
const content = await
|
|
4991
|
+
const content = await readFile6(this.path, "utf8");
|
|
4778
4992
|
if (content.trim() === "") {
|
|
4779
4993
|
return createEmptyState();
|
|
4780
4994
|
}
|
|
@@ -4803,7 +5017,15 @@ async function runConsole(paths, deps) {
|
|
|
4803
5017
|
const sdk = await deps.loadWeixinSdk();
|
|
4804
5018
|
const setIntervalFn = deps.setInterval ?? ((fn, delay) => setInterval(fn, delay));
|
|
4805
5019
|
const clearIntervalFn = deps.clearInterval ?? ((timer) => clearInterval(timer));
|
|
5020
|
+
const addProcessListener = deps.addProcessListener ?? ((signal, handler) => process.on(signal, handler));
|
|
5021
|
+
const removeProcessListener = deps.removeProcessListener ?? ((signal, handler) => process.off(signal, handler));
|
|
4806
5022
|
let heartbeatTimer = null;
|
|
5023
|
+
const shutdownController = new AbortController;
|
|
5024
|
+
const signalHandler = () => {
|
|
5025
|
+
shutdownController.abort();
|
|
5026
|
+
};
|
|
5027
|
+
addProcessListener("SIGINT", signalHandler);
|
|
5028
|
+
addProcessListener("SIGTERM", signalHandler);
|
|
4807
5029
|
try {
|
|
4808
5030
|
if (deps.daemonRuntime) {
|
|
4809
5031
|
await deps.daemonRuntime.start({
|
|
@@ -4818,9 +5040,11 @@ async function runConsole(paths, deps) {
|
|
|
4818
5040
|
console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
|
|
4819
5041
|
await sdk.login();
|
|
4820
5042
|
}
|
|
4821
|
-
await sdk.start(runtime.agent);
|
|
5043
|
+
await sdk.start(runtime.agent, { abortSignal: shutdownController.signal });
|
|
4822
5044
|
} finally {
|
|
4823
5045
|
let disposeError = null;
|
|
5046
|
+
removeProcessListener("SIGINT", signalHandler);
|
|
5047
|
+
removeProcessListener("SIGTERM", signalHandler);
|
|
4824
5048
|
if (heartbeatTimer !== null) {
|
|
4825
5049
|
clearIntervalFn(heartbeatTimer);
|
|
4826
5050
|
}
|
|
@@ -4859,9 +5083,9 @@ class AcpxBridgeClient {
|
|
|
4859
5083
|
request(method, params) {
|
|
4860
5084
|
const id = String(this.nextId);
|
|
4861
5085
|
this.nextId += 1;
|
|
4862
|
-
return awaitable((
|
|
5086
|
+
return awaitable((resolve2, reject) => {
|
|
4863
5087
|
this.pending.set(id, {
|
|
4864
|
-
resolve: (value) =>
|
|
5088
|
+
resolve: (value) => resolve2(value),
|
|
4865
5089
|
reject
|
|
4866
5090
|
});
|
|
4867
5091
|
this.writeLine(encodeBridgeRequest({
|
|
@@ -4960,8 +5184,8 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
4960
5184
|
return client;
|
|
4961
5185
|
}
|
|
4962
5186
|
function awaitable(executor) {
|
|
4963
|
-
return new Promise((
|
|
4964
|
-
executor(
|
|
5187
|
+
return new Promise((resolve2, reject) => {
|
|
5188
|
+
executor(resolve2, reject);
|
|
4965
5189
|
});
|
|
4966
5190
|
}
|
|
4967
5191
|
var init_acpx_bridge_client = __esm(() => {
|
|
@@ -4983,6 +5207,12 @@ class AcpxBridgeTransport {
|
|
|
4983
5207
|
text
|
|
4984
5208
|
});
|
|
4985
5209
|
}
|
|
5210
|
+
async setMode(session, modeId) {
|
|
5211
|
+
await this.client.request("setMode", {
|
|
5212
|
+
...this.toParams(session),
|
|
5213
|
+
modeId
|
|
5214
|
+
});
|
|
5215
|
+
}
|
|
4986
5216
|
async cancel(session) {
|
|
4987
5217
|
return await this.client.request("cancel", this.toParams(session));
|
|
4988
5218
|
}
|
|
@@ -5108,7 +5338,7 @@ import { createRequire as createRequire3 } from "node:module";
|
|
|
5108
5338
|
import { spawn as spawn3 } from "node:child_process";
|
|
5109
5339
|
import { spawn as spawnPty } from "node-pty";
|
|
5110
5340
|
async function defaultRunner(command, args, options) {
|
|
5111
|
-
return await new Promise((
|
|
5341
|
+
return await new Promise((resolve2, reject) => {
|
|
5112
5342
|
const spawnSpec = resolveSpawnCommand(command, args);
|
|
5113
5343
|
const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5114
5344
|
let stdout = "";
|
|
@@ -5131,14 +5361,14 @@ async function defaultRunner(command, args, options) {
|
|
|
5131
5361
|
child.on("close", (code) => {
|
|
5132
5362
|
if (timeoutId)
|
|
5133
5363
|
clearTimeout(timeoutId);
|
|
5134
|
-
|
|
5364
|
+
resolve2({ code: code ?? 1, stdout, stderr });
|
|
5135
5365
|
});
|
|
5136
5366
|
});
|
|
5137
5367
|
}
|
|
5138
5368
|
async function defaultPtyRunner(command, args, options) {
|
|
5139
5369
|
const helperPath = resolveNodePtyHelperPath(require3.resolve("node-pty/package.json"), process.platform, process.arch);
|
|
5140
5370
|
await ensureNodePtyHelperExecutable(helperPath);
|
|
5141
|
-
return await new Promise((
|
|
5371
|
+
return await new Promise((resolve2, reject) => {
|
|
5142
5372
|
const spawnSpec = resolveSpawnCommand(command, args);
|
|
5143
5373
|
const child = spawnPty(spawnSpec.command, spawnSpec.args, {
|
|
5144
5374
|
name: "xterm-color",
|
|
@@ -5158,7 +5388,7 @@ async function defaultPtyRunner(command, args, options) {
|
|
|
5158
5388
|
child.onExit(({ exitCode }) => {
|
|
5159
5389
|
if (timeoutId)
|
|
5160
5390
|
clearTimeout(timeoutId);
|
|
5161
|
-
|
|
5391
|
+
resolve2({ code: exitCode, stdout: output, stderr: "" });
|
|
5162
5392
|
});
|
|
5163
5393
|
});
|
|
5164
5394
|
}
|
|
@@ -5199,6 +5429,14 @@ class AcpxCliTransport {
|
|
|
5199
5429
|
const result = await this.runCommand(this.command, args);
|
|
5200
5430
|
return { text: getPromptText(result) };
|
|
5201
5431
|
}
|
|
5432
|
+
async setMode(session, modeId) {
|
|
5433
|
+
await this.run(this.buildArgs(session, [
|
|
5434
|
+
"set-mode",
|
|
5435
|
+
"-s",
|
|
5436
|
+
session.transportSession,
|
|
5437
|
+
modeId
|
|
5438
|
+
]));
|
|
5439
|
+
}
|
|
5202
5440
|
async cancel(session) {
|
|
5203
5441
|
const output = await this.run(this.buildArgs(session, [
|
|
5204
5442
|
"cancel",
|
|
@@ -5256,7 +5494,7 @@ class AcpxCliTransport {
|
|
|
5256
5494
|
]);
|
|
5257
5495
|
}
|
|
5258
5496
|
async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000) {
|
|
5259
|
-
return await new Promise((
|
|
5497
|
+
return await new Promise((resolve2, reject) => {
|
|
5260
5498
|
const spawnSpec = resolveSpawnCommand(command, args);
|
|
5261
5499
|
const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5262
5500
|
let stdout = "";
|
|
@@ -5298,7 +5536,7 @@ class AcpxCliTransport {
|
|
|
5298
5536
|
if (remaining.length > 0) {
|
|
5299
5537
|
reply(remaining).catch(() => {});
|
|
5300
5538
|
}
|
|
5301
|
-
|
|
5539
|
+
resolve2({ code: code ?? 1, stdout, stderr });
|
|
5302
5540
|
});
|
|
5303
5541
|
});
|
|
5304
5542
|
}
|
|
@@ -5369,7 +5607,7 @@ __export(exports_main, {
|
|
|
5369
5607
|
main: () => main2,
|
|
5370
5608
|
buildApp: () => buildApp
|
|
5371
5609
|
});
|
|
5372
|
-
import { homedir as
|
|
5610
|
+
import { homedir as homedir3 } from "node:os";
|
|
5373
5611
|
import { dirname as dirname8, join as join4 } from "node:path";
|
|
5374
5612
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5375
5613
|
async function buildApp(paths, deps = {}) {
|
|
@@ -5432,7 +5670,7 @@ async function main2() {
|
|
|
5432
5670
|
}
|
|
5433
5671
|
}
|
|
5434
5672
|
function resolveRuntimePaths() {
|
|
5435
|
-
const home = process.env.HOME ??
|
|
5673
|
+
const home = process.env.HOME ?? homedir3();
|
|
5436
5674
|
if (!home) {
|
|
5437
5675
|
throw new Error("Unable to resolve the current user home directory");
|
|
5438
5676
|
}
|
|
@@ -5469,7 +5707,7 @@ var init_main = __esm(async () => {
|
|
|
5469
5707
|
});
|
|
5470
5708
|
|
|
5471
5709
|
// src/cli.ts
|
|
5472
|
-
import { homedir as
|
|
5710
|
+
import { homedir as homedir4 } from "node:os";
|
|
5473
5711
|
import { sep } from "node:path";
|
|
5474
5712
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5475
5713
|
|
|
@@ -5521,15 +5759,23 @@ class DaemonController {
|
|
|
5521
5759
|
startupPollIntervalMs;
|
|
5522
5760
|
startupTimeoutMs;
|
|
5523
5761
|
onStartupPoll;
|
|
5762
|
+
shutdownPollIntervalMs;
|
|
5763
|
+
shutdownTimeoutMs;
|
|
5764
|
+
onShutdownPoll;
|
|
5524
5765
|
constructor(paths, deps) {
|
|
5525
5766
|
this.paths = paths;
|
|
5526
5767
|
this.deps = deps;
|
|
5527
5768
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
5528
5769
|
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
5529
5770
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
|
|
5771
|
+
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
5772
|
+
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
5530
5773
|
this.onStartupPoll = deps.onStartupPoll ?? (async () => {
|
|
5531
5774
|
await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
|
|
5532
5775
|
});
|
|
5776
|
+
this.onShutdownPoll = deps.onShutdownPoll ?? (async () => {
|
|
5777
|
+
await new Promise((resolve) => setTimeout(resolve, this.shutdownPollIntervalMs));
|
|
5778
|
+
});
|
|
5533
5779
|
}
|
|
5534
5780
|
async getStatus() {
|
|
5535
5781
|
const pid = await this.loadPid();
|
|
@@ -5568,6 +5814,7 @@ class DaemonController {
|
|
|
5568
5814
|
}
|
|
5569
5815
|
if (this.deps.isProcessRunning(pid)) {
|
|
5570
5816
|
await this.deps.terminateProcess(pid);
|
|
5817
|
+
await this.waitForShutdown(pid);
|
|
5571
5818
|
}
|
|
5572
5819
|
await this.clearRuntimeFiles();
|
|
5573
5820
|
return { state: "stopped", detail: "stopped" };
|
|
@@ -5608,6 +5855,19 @@ class DaemonController {
|
|
|
5608
5855
|
}
|
|
5609
5856
|
throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
|
|
5610
5857
|
}
|
|
5858
|
+
async waitForShutdown(pid) {
|
|
5859
|
+
const deadline = Date.now() + this.shutdownTimeoutMs;
|
|
5860
|
+
while (Date.now() < deadline) {
|
|
5861
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
await this.onShutdownPoll();
|
|
5865
|
+
}
|
|
5866
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
5867
|
+
return;
|
|
5868
|
+
}
|
|
5869
|
+
throw new Error(`weacpx daemon did not exit within ${this.shutdownTimeoutMs}ms (pid ${pid})`);
|
|
5870
|
+
}
|
|
5611
5871
|
}
|
|
5612
5872
|
|
|
5613
5873
|
// src/daemon/create-daemon-controller.ts
|
|
@@ -5843,7 +6103,15 @@ class DaemonRuntime {
|
|
|
5843
6103
|
}
|
|
5844
6104
|
|
|
5845
6105
|
// src/cli.ts
|
|
5846
|
-
var HELP_LINES = [
|
|
6106
|
+
var HELP_LINES = [
|
|
6107
|
+
"用法:",
|
|
6108
|
+
"weacpx login - 微信登录",
|
|
6109
|
+
"weacpx logout - 退出登录",
|
|
6110
|
+
"weacpx run - 前台运行",
|
|
6111
|
+
"weacpx start - 后台启动",
|
|
6112
|
+
"weacpx status - 查看状态",
|
|
6113
|
+
"weacpx stop - 停止服务"
|
|
6114
|
+
];
|
|
5847
6115
|
async function runCli(args, deps = {}) {
|
|
5848
6116
|
const command = args[0];
|
|
5849
6117
|
const print = deps.print ?? ((line) => console.log(line));
|
|
@@ -5937,7 +6205,7 @@ function createDefaultController() {
|
|
|
5937
6205
|
});
|
|
5938
6206
|
}
|
|
5939
6207
|
function requireHome() {
|
|
5940
|
-
const home = process.env.HOME ??
|
|
6208
|
+
const home = process.env.HOME ?? homedir4();
|
|
5941
6209
|
if (!home) {
|
|
5942
6210
|
throw new Error("Unable to resolve the current user home directory");
|
|
5943
6211
|
}
|
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
|
+
"version": "0.1.6",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
7
7
|
"doc": "docs",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test:smoke": "node ./scripts/run-tests.mjs tests/smoke"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"acpx": "^0.
|
|
49
|
+
"acpx": "^0.4.0",
|
|
50
50
|
"node-pty": "^1.1.0",
|
|
51
51
|
"qrcode-terminal": "^0.12.0"
|
|
52
52
|
},
|
|
@@ -55,5 +55,10 @@
|
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=22"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/bun": "^1.3.11",
|
|
61
|
+
"bun-types": "^1.3.11",
|
|
62
|
+
"typescript": "^6.0.2"
|
|
58
63
|
}
|
|
59
64
|
}
|