weacpx 0.1.1 → 0.1.3
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 +70 -59
- package/dist/bridge/bridge-main.js +157 -10
- package/dist/cli.js +246 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
# weacpx
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## weacpx 是什么
|
|
6
|
-
|
|
7
|
-
`weacpx` 基于以下组件工作:
|
|
8
|
-
|
|
9
|
-
- `weixin-agent-sdk`
|
|
10
|
-
- `acpx`
|
|
11
|
-
- `acpx` 已支持的 agent driver,或自定义 ACP agent
|
|
12
|
-
|
|
13
|
-
它适合这样的场景:
|
|
14
|
-
|
|
15
|
-
- 你已经在本机使用 `acpx`
|
|
16
|
-
- 你希望通过微信远程发起或继续一个 agent 会话
|
|
17
|
-
- 你希望在手机上完成常见的会话切换、目录切换和对话操作
|
|
3
|
+
使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude Code、Codex 等 Agents。
|
|
18
4
|
|
|
19
5
|
## 安装前准备
|
|
20
6
|
|
|
21
7
|
开始前,至少需要:
|
|
22
8
|
|
|
23
|
-
- Node.js 22+
|
|
24
|
-
- Bun
|
|
9
|
+
- Node.js 22+ 或 Bun
|
|
25
10
|
- 一个可用的微信登录环境
|
|
26
|
-
-
|
|
11
|
+
- Claude Code 或 Codex
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
> `weacpx` 基于 `weixin-agent-sdk` 与 `acpx` 实现。
|
|
14
|
+
> 正常情况下,不需要再额外全局安装 `acpx`。
|
|
29
15
|
|
|
30
16
|
## 安装
|
|
31
17
|
|
|
@@ -34,17 +20,10 @@
|
|
|
34
20
|
```bash
|
|
35
21
|
# 使用 NPM 全局安装
|
|
36
22
|
npm install -g weacpx
|
|
37
|
-
# 或使用
|
|
23
|
+
# 或使用 Bun 全局安装
|
|
38
24
|
bun add -g weacpx
|
|
39
25
|
```
|
|
40
26
|
|
|
41
|
-
如果你是从源码仓库直接使用,请先安装依赖并构建:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
bun install
|
|
45
|
-
bun run dev
|
|
46
|
-
```
|
|
47
|
-
|
|
48
27
|
## 快速开始
|
|
49
28
|
|
|
50
29
|
第一次使用建议按这个顺序:
|
|
@@ -58,19 +37,9 @@ bun run dev
|
|
|
58
37
|
```bash
|
|
59
38
|
weacpx login
|
|
60
39
|
weacpx start
|
|
61
|
-
weacpx status
|
|
62
40
|
```
|
|
63
41
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
bun run login
|
|
68
|
-
bun run dev
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
`weacpx login` 和 `bun run login` 都会在终端里显示二维码。
|
|
72
|
-
|
|
73
|
-
启动后,在微信里先发:
|
|
42
|
+
`weacpx login` 会在终端里显示二维码,使用微信扫描登录。`weacpx start` 启动后,在微信里发:
|
|
74
43
|
|
|
75
44
|
```text
|
|
76
45
|
/ss codex -d /absolute/path/to/your/repo
|
|
@@ -78,8 +47,9 @@ bun run dev
|
|
|
78
47
|
/help
|
|
79
48
|
```
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
|
|
50
|
+
`/ss codex -d /absolute/path/to/your/repo`:开启或挂在一个会话,并切换到该会话。使用 Codex,并指定工作目录为 `/absolute/path/to/your/repo`。
|
|
51
|
+
|
|
52
|
+
`/help` 查看帮助信息。
|
|
83
53
|
|
|
84
54
|
然后就可以直接发普通消息,例如:
|
|
85
55
|
|
|
@@ -87,6 +57,17 @@ bun run dev
|
|
|
87
57
|
hello
|
|
88
58
|
```
|
|
89
59
|
|
|
60
|
+
如果你是从源码仓库直接使用:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 先安装依赖
|
|
64
|
+
bun install
|
|
65
|
+
# 登录微信
|
|
66
|
+
bun run login
|
|
67
|
+
# 启动服务
|
|
68
|
+
bun run dev
|
|
69
|
+
```
|
|
70
|
+
|
|
90
71
|
普通文本会默认发送到当前选中的 session。
|
|
91
72
|
|
|
92
73
|
## CLI 命令
|
|
@@ -106,27 +87,55 @@ hello
|
|
|
106
87
|
- `status` 查看后台状态、PID、配置路径和日志路径
|
|
107
88
|
- `stop` 停止后台实例
|
|
108
89
|
|
|
109
|
-
##
|
|
90
|
+
## 微信中使用说明
|
|
110
91
|
|
|
111
|
-
### Agent
|
|
92
|
+
### 管理 Agent
|
|
112
93
|
|
|
113
|
-
内置 `codex` 与 `claude`
|
|
94
|
+
内置 `codex` 与 `claude` 两个常见 agent,也支持添加你自己的 agents。
|
|
114
95
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
| 命令 | 说明 |
|
|
97
|
+
|------|------|
|
|
98
|
+
| `/agents` | 查看当前已添加的 agent |
|
|
99
|
+
| `/agent add codex` | 添加 codex agent |
|
|
100
|
+
| `/agent add claude` | 添加 claude agent |
|
|
101
|
+
| `/agent rm <name>` | 删除 agent |
|
|
119
102
|
|
|
120
103
|
说明:
|
|
121
104
|
|
|
122
105
|
- 内置 `codex` 和 `claude` 走 `acpx` 的 driver alias,通常不需要额外写 `agent.command`
|
|
123
106
|
- 如果你接入的是自定义 agent,再考虑显式配置 `agent.command`
|
|
124
107
|
|
|
108
|
+
`config.json` 中的 `agent.command` 用于显式指定 agent 的原始启动命令,完整字段如下:
|
|
109
|
+
|
|
110
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
111
|
+
|------|------|------|------|
|
|
112
|
+
| `driver` | `string` | 是 | agent 驱动类型,传递给 acpx 的第一位置参数 |
|
|
113
|
+
| `command` | `string` | 否 | 显式指定自定义 agent 的原始命令。不填则使用 acpx 默认行为 |
|
|
114
|
+
|
|
115
|
+
示例 — 配置一个自定义 agent:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"agents": {
|
|
120
|
+
"my-agent": {
|
|
121
|
+
"driver": "codex",
|
|
122
|
+
"command": "/path/to/acpx codex --arg1 value1"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- 内置 `codex` 和 `claude` 建议只写 `driver`,让 `acpx` 自己解析对应 alias
|
|
129
|
+
- `command` 主要用于自定义 agent,不建议给内置 driver 手写原始命令
|
|
130
|
+
- 旧版 `codex` raw command 配置会被自动忽略,回退为 `acpx codex ...`
|
|
131
|
+
|
|
125
132
|
### Workspace 工作目录
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
| 命令 | 说明 |
|
|
135
|
+
|------|------|
|
|
136
|
+
| `/workspaces` / `/workspace` / `/ws` | 查看当前已添加的工作目录 |
|
|
137
|
+
| `/ws new <name> -d <path>` | 添加工作目录,`-d` 后面接的是目录在电脑的绝对路径 |
|
|
138
|
+
| `/workspace rm <name>` | 删除工作目录 |
|
|
130
139
|
|
|
131
140
|
说明:
|
|
132
141
|
|
|
@@ -135,15 +144,17 @@ hello
|
|
|
135
144
|
|
|
136
145
|
### Session 会话
|
|
137
146
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
| 命令 | 说明 |
|
|
148
|
+
|------|------|
|
|
149
|
+
| `/sessions` / `/session` / `/ss` | 查看当前已添加的会话 |
|
|
150
|
+
| `/ss <agent> -d <path>` | 新建会话(自动按目录名推导并创建或复用 workspace,再创建或复用 session) |
|
|
151
|
+
| `/ss new <agent> -d <path>` | 强制新建会话 |
|
|
152
|
+
| `/ss new <alias> -a <name> --ws <name>` | 强制新建会话,并指定 agent 和 workspace |
|
|
153
|
+
| `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
|
|
154
|
+
| `/use <alias>` | 切换当前会话 |
|
|
155
|
+
| `/status` | 查看当前会话状态 |
|
|
156
|
+
| `/cancel` | 取消当前会话 |
|
|
157
|
+
| `/stop` | 停止当前会话 |
|
|
147
158
|
|
|
148
159
|
说明:
|
|
149
160
|
|
|
@@ -28,6 +28,138 @@ var __export = (target, all) => {
|
|
|
28
28
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
29
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
30
|
|
|
31
|
+
// src/process/spawn-command.ts
|
|
32
|
+
function resolveSpawnCommand(command, args) {
|
|
33
|
+
if (SCRIPT_FILE_PATTERN.test(command)) {
|
|
34
|
+
return {
|
|
35
|
+
command: process.execPath,
|
|
36
|
+
args: [command, ...args]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return { command, args };
|
|
40
|
+
}
|
|
41
|
+
var SCRIPT_FILE_PATTERN;
|
|
42
|
+
var init_spawn_command = __esm(() => {
|
|
43
|
+
SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// src/transport/prompt-output.ts
|
|
47
|
+
function getPromptText(result) {
|
|
48
|
+
const stdoutOutput = extractPromptOutput(result.stdout);
|
|
49
|
+
if (result.code === 0) {
|
|
50
|
+
return sanitizePromptText(stdoutOutput.text);
|
|
51
|
+
}
|
|
52
|
+
const preferredError = extractPromptFailureMessage(result);
|
|
53
|
+
if (preferredError) {
|
|
54
|
+
throw new Error(preferredError);
|
|
55
|
+
}
|
|
56
|
+
const stderrOutput = extractPromptOutput(result.stderr);
|
|
57
|
+
const partialReply = [stdoutOutput, stderrOutput].filter((output) => output.hasAgentMessage && output.text.length > 0).map((output) => sanitizePromptText(output.text)).find((text) => text.length > 0);
|
|
58
|
+
if (partialReply) {
|
|
59
|
+
return partialReply;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`command failed with exit code ${result.code}`);
|
|
62
|
+
}
|
|
63
|
+
function normalizeCommandError(result) {
|
|
64
|
+
const preferredError = extractPromptFailureMessage(result);
|
|
65
|
+
if (preferredError) {
|
|
66
|
+
return preferredError;
|
|
67
|
+
}
|
|
68
|
+
return result.stdout.trim() || null;
|
|
69
|
+
}
|
|
70
|
+
function extractPromptFailureMessage(result) {
|
|
71
|
+
const rpcMessages = extractJsonRpcErrorMessages(result.stderr).concat(extractJsonRpcErrorMessages(result.stdout)).filter((message) => message.length > 0);
|
|
72
|
+
const preferredMessage = [...rpcMessages].reverse().find((message) => message !== "Resource not found");
|
|
73
|
+
if (preferredMessage) {
|
|
74
|
+
return preferredMessage;
|
|
75
|
+
}
|
|
76
|
+
if (rpcMessages.length > 0) {
|
|
77
|
+
return rpcMessages[rpcMessages.length - 1] ?? null;
|
|
78
|
+
}
|
|
79
|
+
const stderrText = result.stderr.trim();
|
|
80
|
+
if (stderrText.length > 0) {
|
|
81
|
+
return stderrText;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function extractPromptOutput(output) {
|
|
86
|
+
const lines = output.split(`
|
|
87
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
88
|
+
const messageSegments = [];
|
|
89
|
+
let currentSegment = "";
|
|
90
|
+
let hasAgentMessage = false;
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
try {
|
|
93
|
+
const event = JSON.parse(line);
|
|
94
|
+
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
95
|
+
if (isMessageChunk) {
|
|
96
|
+
hasAgentMessage = true;
|
|
97
|
+
const chunk = event.params.update.content.text ?? "";
|
|
98
|
+
if (chunk.length > 0) {
|
|
99
|
+
currentSegment += chunk;
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (currentSegment.trim().length > 0) {
|
|
104
|
+
messageSegments.push(currentSegment.trim());
|
|
105
|
+
}
|
|
106
|
+
currentSegment = "";
|
|
107
|
+
} catch {
|
|
108
|
+
if (currentSegment.trim().length > 0) {
|
|
109
|
+
messageSegments.push(currentSegment.trim());
|
|
110
|
+
currentSegment = "";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (currentSegment.trim().length > 0) {
|
|
115
|
+
messageSegments.push(currentSegment.trim());
|
|
116
|
+
}
|
|
117
|
+
if (messageSegments.length > 0) {
|
|
118
|
+
return {
|
|
119
|
+
text: messageSegments[messageSegments.length - 1],
|
|
120
|
+
hasAgentMessage
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
text: output.trim(),
|
|
125
|
+
hasAgentMessage
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function sanitizePromptText(text) {
|
|
129
|
+
const trimmed = text.trim();
|
|
130
|
+
const paragraphs = trimmed.split(/\n\s*\n/);
|
|
131
|
+
if (paragraphs.length < 2) {
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
134
|
+
const firstParagraph = paragraphs[0].trim().replace(/\s+/g, " ").toLowerCase();
|
|
135
|
+
if (!looksLikeWorkflowPreamble(firstParagraph)) {
|
|
136
|
+
return trimmed;
|
|
137
|
+
}
|
|
138
|
+
return paragraphs.slice(1).join(`
|
|
139
|
+
|
|
140
|
+
`).trim();
|
|
141
|
+
}
|
|
142
|
+
function looksLikeWorkflowPreamble(paragraph) {
|
|
143
|
+
if (!paragraph.startsWith("using ")) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return paragraph.includes("using-superpowers") || paragraph.includes("repo workflow requirement") || paragraph.includes("workflow requirement") || paragraph.includes("before responding") || paragraph.includes("skill check");
|
|
147
|
+
}
|
|
148
|
+
function extractJsonRpcErrorMessages(output) {
|
|
149
|
+
return output.split(`
|
|
150
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
|
|
151
|
+
try {
|
|
152
|
+
const payload = JSON.parse(line);
|
|
153
|
+
if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
|
|
154
|
+
return [payload.error.message];
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
return [];
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
31
163
|
// src/bridge/bridge-main.ts
|
|
32
164
|
import { createInterface } from "node:readline";
|
|
33
165
|
|
|
@@ -108,6 +240,7 @@ function asOptionalString(value) {
|
|
|
108
240
|
}
|
|
109
241
|
|
|
110
242
|
// src/bridge/bridge-runtime.ts
|
|
243
|
+
init_spawn_command();
|
|
111
244
|
import { spawn } from "node:child_process";
|
|
112
245
|
import { fileURLToPath } from "node:url";
|
|
113
246
|
|
|
@@ -121,46 +254,54 @@ class BridgeRuntime {
|
|
|
121
254
|
this.runSessionCreate = runSessionCreate;
|
|
122
255
|
}
|
|
123
256
|
async hasSession(input) {
|
|
124
|
-
const
|
|
257
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
125
258
|
"sessions",
|
|
126
259
|
"show",
|
|
127
260
|
input.name
|
|
128
261
|
]));
|
|
262
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
129
263
|
return { exists: result.code === 0 };
|
|
130
264
|
}
|
|
131
265
|
async ensureSession(input) {
|
|
132
|
-
const
|
|
266
|
+
const ensuredSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
267
|
+
"sessions",
|
|
268
|
+
"ensure",
|
|
269
|
+
"--name",
|
|
270
|
+
input.name
|
|
271
|
+
]));
|
|
272
|
+
const ensured = await this.run(ensuredSpec.command, ensuredSpec.args);
|
|
133
273
|
if (ensured.code === 0) {
|
|
134
274
|
return {};
|
|
135
275
|
}
|
|
136
|
-
const
|
|
276
|
+
const existingSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, ["sessions", "show", input.name]));
|
|
277
|
+
const existing = await this.run(existingSpec.command, existingSpec.args);
|
|
137
278
|
if (existing.code === 0) {
|
|
138
279
|
return {};
|
|
139
280
|
}
|
|
140
|
-
const
|
|
281
|
+
const createSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, ["sessions", "new", "--name", input.name]));
|
|
282
|
+
const createdWithHelper = await this.runSessionCreate(createSpec.command, createSpec.args, input.cwd);
|
|
141
283
|
if (createdWithHelper.code !== 0) {
|
|
142
284
|
throw new Error(createdWithHelper.stderr || createdWithHelper.stdout || ensured.stderr || ensured.stdout || "failed to create session");
|
|
143
285
|
}
|
|
144
286
|
return {};
|
|
145
287
|
}
|
|
146
288
|
async prompt(input) {
|
|
147
|
-
const
|
|
289
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
|
|
148
290
|
"prompt",
|
|
149
291
|
"-s",
|
|
150
292
|
input.name,
|
|
151
293
|
input.text
|
|
152
294
|
]));
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
return { text: result.stdout.trim() };
|
|
295
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
296
|
+
return { text: getPromptText(result) };
|
|
157
297
|
}
|
|
158
298
|
async cancel(input) {
|
|
159
|
-
const
|
|
299
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
160
300
|
"cancel",
|
|
161
301
|
"-s",
|
|
162
302
|
input.name
|
|
163
303
|
]));
|
|
304
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
164
305
|
if (result.code !== 0) {
|
|
165
306
|
throw new Error(result.stderr || result.stdout || "cancel failed");
|
|
166
307
|
}
|
|
@@ -178,6 +319,12 @@ class BridgeRuntime {
|
|
|
178
319
|
}
|
|
179
320
|
return ["--format", "quiet", "--cwd", input.cwd, input.agent, ...tail];
|
|
180
321
|
}
|
|
322
|
+
buildPromptArgs(input, tail) {
|
|
323
|
+
if (input.agentCommand) {
|
|
324
|
+
return ["--format", "json", "--json-strict", "--cwd", input.cwd, "--agent", input.agentCommand, ...tail];
|
|
325
|
+
}
|
|
326
|
+
return ["--format", "json", "--json-strict", "--cwd", input.cwd, input.agent, ...tail];
|
|
327
|
+
}
|
|
181
328
|
}
|
|
182
329
|
async function defaultRunner(command, args) {
|
|
183
330
|
return await new Promise((resolve, reject) => {
|
package/dist/cli.js
CHANGED
|
@@ -1440,6 +1440,9 @@ function parseCommand(input) {
|
|
|
1440
1440
|
}
|
|
1441
1441
|
return { kind: "session.attach", alias, agent, workspace, transportSession };
|
|
1442
1442
|
}
|
|
1443
|
+
if (command.startsWith("/") && isRecognizedCommand(command)) {
|
|
1444
|
+
return { kind: "invalid", text: trimmed, recognizedCommand: command };
|
|
1445
|
+
}
|
|
1443
1446
|
return { kind: "prompt", text: trimmed };
|
|
1444
1447
|
}
|
|
1445
1448
|
function hasAnyFlag(parts, flags) {
|
|
@@ -1462,6 +1465,9 @@ function normalizeCommand(command) {
|
|
|
1462
1465
|
return "/cancel";
|
|
1463
1466
|
return command;
|
|
1464
1467
|
}
|
|
1468
|
+
function isRecognizedCommand(command) {
|
|
1469
|
+
return RECOGNIZED_COMMANDS.has(command);
|
|
1470
|
+
}
|
|
1465
1471
|
function tokenizeCommand(input) {
|
|
1466
1472
|
const tokens = [];
|
|
1467
1473
|
let current = "";
|
|
@@ -1493,10 +1499,26 @@ function tokenizeCommand(input) {
|
|
|
1493
1499
|
}
|
|
1494
1500
|
return tokens;
|
|
1495
1501
|
}
|
|
1502
|
+
var RECOGNIZED_COMMANDS;
|
|
1503
|
+
var init_parse_command = __esm(() => {
|
|
1504
|
+
RECOGNIZED_COMMANDS = new Set([
|
|
1505
|
+
"/help",
|
|
1506
|
+
"/agents",
|
|
1507
|
+
"/workspaces",
|
|
1508
|
+
"/sessions",
|
|
1509
|
+
"/status",
|
|
1510
|
+
"/cancel",
|
|
1511
|
+
"/session",
|
|
1512
|
+
"/workspace",
|
|
1513
|
+
"/use",
|
|
1514
|
+
"/agent"
|
|
1515
|
+
]);
|
|
1516
|
+
});
|
|
1496
1517
|
|
|
1497
1518
|
// src/commands/command-router.ts
|
|
1498
1519
|
import { access } from "node:fs/promises";
|
|
1499
1520
|
import { basename as basename2, normalize } from "node:path";
|
|
1521
|
+
import { homedir } from "node:os";
|
|
1500
1522
|
|
|
1501
1523
|
class CommandRouter {
|
|
1502
1524
|
sessions;
|
|
@@ -1520,6 +1542,19 @@ class CommandRouter {
|
|
|
1520
1542
|
});
|
|
1521
1543
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
1522
1544
|
switch (command.kind) {
|
|
1545
|
+
case "invalid":
|
|
1546
|
+
return {
|
|
1547
|
+
text: [
|
|
1548
|
+
"无法识别的命令格式。",
|
|
1549
|
+
"",
|
|
1550
|
+
"正确的会话创建格式:",
|
|
1551
|
+
"/session new <别名> --agent <Agent名> --ws <工作区名>",
|
|
1552
|
+
"",
|
|
1553
|
+
"例如:",
|
|
1554
|
+
"/session new demo --agent claude --ws weacpx"
|
|
1555
|
+
].join(`
|
|
1556
|
+
`)
|
|
1557
|
+
};
|
|
1523
1558
|
case "help":
|
|
1524
1559
|
return { text: renderHelpText() };
|
|
1525
1560
|
case "agents":
|
|
@@ -1553,10 +1588,11 @@ class CommandRouter {
|
|
|
1553
1588
|
if (!this.config || !this.configStore) {
|
|
1554
1589
|
return { text: "当前没有加载可写入的配置。" };
|
|
1555
1590
|
}
|
|
1556
|
-
|
|
1591
|
+
const wsCwd = normalizePathForWorkspace(command.cwd);
|
|
1592
|
+
if (!await pathExists(wsCwd)) {
|
|
1557
1593
|
return { text: `工作区路径不存在:${command.cwd}` };
|
|
1558
1594
|
}
|
|
1559
|
-
const updated = await this.configStore.upsertWorkspace(command.name,
|
|
1595
|
+
const updated = await this.configStore.upsertWorkspace(command.name, wsCwd);
|
|
1560
1596
|
this.replaceConfig(updated);
|
|
1561
1597
|
return { text: `工作区「${command.name}」已保存` };
|
|
1562
1598
|
}
|
|
@@ -1755,7 +1791,17 @@ class CommandRouter {
|
|
|
1755
1791
|
`)
|
|
1756
1792
|
};
|
|
1757
1793
|
}
|
|
1758
|
-
|
|
1794
|
+
if (!isPartialPromptOutputError(message)) {
|
|
1795
|
+
throw error;
|
|
1796
|
+
}
|
|
1797
|
+
return {
|
|
1798
|
+
text: [
|
|
1799
|
+
`当前会话「${session.alias}」执行中断,未收到最终回复。`,
|
|
1800
|
+
"请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
|
|
1801
|
+
`错误信息:${summarizeTransportError(message)}`
|
|
1802
|
+
].join(`
|
|
1803
|
+
`)
|
|
1804
|
+
};
|
|
1759
1805
|
}
|
|
1760
1806
|
renderSessionCreationError(session, error) {
|
|
1761
1807
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1891,7 +1937,8 @@ async function pathExists(path) {
|
|
|
1891
1937
|
}
|
|
1892
1938
|
}
|
|
1893
1939
|
function normalizePathForWorkspace(path) {
|
|
1894
|
-
|
|
1940
|
+
const expanded = path.startsWith("~") ? homedir() + path.slice(1) : path;
|
|
1941
|
+
return normalize(expanded);
|
|
1895
1942
|
}
|
|
1896
1943
|
function sameWorkspacePath(left, right) {
|
|
1897
1944
|
const normalizedLeft = normalizePathForWorkspace(left);
|
|
@@ -1901,9 +1948,16 @@ function sameWorkspacePath(left, right) {
|
|
|
1901
1948
|
}
|
|
1902
1949
|
return normalizedLeft === normalizedRight;
|
|
1903
1950
|
}
|
|
1951
|
+
function summarizeTransportError(message) {
|
|
1952
|
+
return message.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
1953
|
+
}
|
|
1954
|
+
function isPartialPromptOutputError(message) {
|
|
1955
|
+
return message.includes("未收到最终回复");
|
|
1956
|
+
}
|
|
1904
1957
|
var init_command_router = __esm(() => {
|
|
1905
1958
|
init_agent_templates();
|
|
1906
1959
|
init_app_logger();
|
|
1960
|
+
init_parse_command();
|
|
1907
1961
|
});
|
|
1908
1962
|
|
|
1909
1963
|
// src/config/resolve-agent-command.ts
|
|
@@ -2108,7 +2162,7 @@ var init_ensure_config = __esm(() => {
|
|
|
2108
2162
|
|
|
2109
2163
|
// src/config/resolve-acpx-command.ts
|
|
2110
2164
|
import { readFileSync } from "node:fs";
|
|
2111
|
-
import {
|
|
2165
|
+
import { posix, win32 } from "node:path";
|
|
2112
2166
|
import { createRequire as createRequire2 } from "node:module";
|
|
2113
2167
|
function resolveAcpxCommand(options = {}) {
|
|
2114
2168
|
if (options.configuredCommand) {
|
|
@@ -2120,12 +2174,11 @@ function resolveAcpxCommand(options = {}) {
|
|
|
2120
2174
|
try {
|
|
2121
2175
|
const packageJsonPath = resolvePackageJson("acpx/package.json");
|
|
2122
2176
|
const pkg = readPackageJson(packageJsonPath);
|
|
2177
|
+
const pathApi = platform === "win32" ? win32 : posix;
|
|
2178
|
+
const packageDir = pathApi.dirname(packageJsonPath);
|
|
2123
2179
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && typeof pkg.bin.acpx === "string" ? pkg.bin.acpx : null;
|
|
2124
2180
|
if (binPath) {
|
|
2125
|
-
|
|
2126
|
-
return resolve(dirname6(packageJsonPath), "../.bin/acpx.exe");
|
|
2127
|
-
}
|
|
2128
|
-
return resolve(dirname6(packageJsonPath), binPath);
|
|
2181
|
+
return pathApi.resolve(packageDir, binPath);
|
|
2129
2182
|
}
|
|
2130
2183
|
} catch {}
|
|
2131
2184
|
return "acpx";
|
|
@@ -2275,7 +2328,7 @@ function createEmptyState() {
|
|
|
2275
2328
|
|
|
2276
2329
|
// src/state/state-store.ts
|
|
2277
2330
|
import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2278
|
-
import { dirname as
|
|
2331
|
+
import { dirname as dirname6 } from "node:path";
|
|
2279
2332
|
|
|
2280
2333
|
class StateStore {
|
|
2281
2334
|
path;
|
|
@@ -2297,7 +2350,7 @@ class StateStore {
|
|
|
2297
2350
|
}
|
|
2298
2351
|
}
|
|
2299
2352
|
async save(state) {
|
|
2300
|
-
await mkdir7(
|
|
2353
|
+
await mkdir7(dirname6(this.path), { recursive: true });
|
|
2301
2354
|
await writeFile5(this.path, JSON.stringify(state, null, 2));
|
|
2302
2355
|
}
|
|
2303
2356
|
}
|
|
@@ -2320,17 +2373,27 @@ async function runConsole(paths, deps) {
|
|
|
2320
2373
|
configPath: paths.configPath,
|
|
2321
2374
|
statePath: paths.statePath
|
|
2322
2375
|
});
|
|
2323
|
-
heartbeatTimer = setIntervalFn(() =>
|
|
2376
|
+
heartbeatTimer = setIntervalFn(() => {
|
|
2377
|
+
deps.daemonRuntime?.heartbeat().catch(() => {});
|
|
2378
|
+
}, deps.heartbeatIntervalMs ?? 30000);
|
|
2324
2379
|
}
|
|
2325
2380
|
await sdk.start(runtime.agent);
|
|
2326
2381
|
} finally {
|
|
2382
|
+
let disposeError = null;
|
|
2327
2383
|
if (heartbeatTimer !== null) {
|
|
2328
2384
|
clearIntervalFn(heartbeatTimer);
|
|
2329
2385
|
}
|
|
2330
|
-
|
|
2386
|
+
try {
|
|
2387
|
+
await runtime.dispose();
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
disposeError = error;
|
|
2390
|
+
}
|
|
2331
2391
|
if (deps.daemonRuntime) {
|
|
2332
2392
|
await deps.daemonRuntime.stop();
|
|
2333
2393
|
}
|
|
2394
|
+
if (disposeError) {
|
|
2395
|
+
throw disposeError;
|
|
2396
|
+
}
|
|
2334
2397
|
}
|
|
2335
2398
|
}
|
|
2336
2399
|
|
|
@@ -2355,8 +2418,8 @@ class AcpxBridgeClient {
|
|
|
2355
2418
|
request(method, params) {
|
|
2356
2419
|
const id = String(this.nextId);
|
|
2357
2420
|
this.nextId += 1;
|
|
2358
|
-
return awaitable((
|
|
2359
|
-
this.pending.set(id, { resolve
|
|
2421
|
+
return awaitable((resolve, reject) => {
|
|
2422
|
+
this.pending.set(id, { resolve, reject });
|
|
2360
2423
|
this.writeLine(encodeBridgeRequest({
|
|
2361
2424
|
id,
|
|
2362
2425
|
method,
|
|
@@ -2377,6 +2440,13 @@ class AcpxBridgeClient {
|
|
|
2377
2440
|
}
|
|
2378
2441
|
pending.reject(new Error(response.error.message));
|
|
2379
2442
|
}
|
|
2443
|
+
handleExit(error) {
|
|
2444
|
+
const pendingRequests = [...this.pending.values()];
|
|
2445
|
+
this.pending.clear();
|
|
2446
|
+
for (const pending of pendingRequests) {
|
|
2447
|
+
pending.reject(error);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2380
2450
|
}
|
|
2381
2451
|
function buildBridgeSpawnSpec(options) {
|
|
2382
2452
|
if (options.execPath.endsWith("bun")) {
|
|
@@ -2416,6 +2486,10 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
2416
2486
|
});
|
|
2417
2487
|
child.on("exit", () => {
|
|
2418
2488
|
output.close();
|
|
2489
|
+
client.handleExit(new Error("bridge process exited before responding"));
|
|
2490
|
+
});
|
|
2491
|
+
child.on("error", (error) => {
|
|
2492
|
+
client.handleExit(error);
|
|
2419
2493
|
});
|
|
2420
2494
|
client.waitUntilReady = async () => {
|
|
2421
2495
|
await client.request("ping", {});
|
|
@@ -2432,8 +2506,8 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
2432
2506
|
return client;
|
|
2433
2507
|
}
|
|
2434
2508
|
function awaitable(executor) {
|
|
2435
|
-
return new Promise((
|
|
2436
|
-
executor(
|
|
2509
|
+
return new Promise((resolve, reject) => {
|
|
2510
|
+
executor(resolve, reject);
|
|
2437
2511
|
});
|
|
2438
2512
|
}
|
|
2439
2513
|
var init_acpx_bridge_client = () => {};
|
|
@@ -2476,14 +2550,146 @@ class AcpxBridgeTransport {
|
|
|
2476
2550
|
}
|
|
2477
2551
|
}
|
|
2478
2552
|
|
|
2553
|
+
// src/process/spawn-command.ts
|
|
2554
|
+
function resolveSpawnCommand(command, args) {
|
|
2555
|
+
if (SCRIPT_FILE_PATTERN.test(command)) {
|
|
2556
|
+
return {
|
|
2557
|
+
command: process.execPath,
|
|
2558
|
+
args: [command, ...args]
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
return { command, args };
|
|
2562
|
+
}
|
|
2563
|
+
var SCRIPT_FILE_PATTERN;
|
|
2564
|
+
var init_spawn_command = __esm(() => {
|
|
2565
|
+
SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2568
|
+
// src/transport/prompt-output.ts
|
|
2569
|
+
function getPromptText(result) {
|
|
2570
|
+
const stdoutOutput = extractPromptOutput(result.stdout);
|
|
2571
|
+
if (result.code === 0) {
|
|
2572
|
+
return sanitizePromptText(stdoutOutput.text);
|
|
2573
|
+
}
|
|
2574
|
+
const preferredError = extractPromptFailureMessage(result);
|
|
2575
|
+
if (preferredError) {
|
|
2576
|
+
throw new Error(preferredError);
|
|
2577
|
+
}
|
|
2578
|
+
const stderrOutput = extractPromptOutput(result.stderr);
|
|
2579
|
+
const partialReply = [stdoutOutput, stderrOutput].filter((output) => output.hasAgentMessage && output.text.length > 0).map((output) => sanitizePromptText(output.text)).find((text) => text.length > 0);
|
|
2580
|
+
if (partialReply) {
|
|
2581
|
+
return partialReply;
|
|
2582
|
+
}
|
|
2583
|
+
throw new Error(`command failed with exit code ${result.code}`);
|
|
2584
|
+
}
|
|
2585
|
+
function normalizeCommandError(result) {
|
|
2586
|
+
const preferredError = extractPromptFailureMessage(result);
|
|
2587
|
+
if (preferredError) {
|
|
2588
|
+
return preferredError;
|
|
2589
|
+
}
|
|
2590
|
+
return result.stdout.trim() || null;
|
|
2591
|
+
}
|
|
2592
|
+
function extractPromptFailureMessage(result) {
|
|
2593
|
+
const rpcMessages = extractJsonRpcErrorMessages(result.stderr).concat(extractJsonRpcErrorMessages(result.stdout)).filter((message) => message.length > 0);
|
|
2594
|
+
const preferredMessage = [...rpcMessages].reverse().find((message) => message !== "Resource not found");
|
|
2595
|
+
if (preferredMessage) {
|
|
2596
|
+
return preferredMessage;
|
|
2597
|
+
}
|
|
2598
|
+
if (rpcMessages.length > 0) {
|
|
2599
|
+
return rpcMessages[rpcMessages.length - 1] ?? null;
|
|
2600
|
+
}
|
|
2601
|
+
const stderrText = result.stderr.trim();
|
|
2602
|
+
if (stderrText.length > 0) {
|
|
2603
|
+
return stderrText;
|
|
2604
|
+
}
|
|
2605
|
+
return null;
|
|
2606
|
+
}
|
|
2607
|
+
function extractPromptOutput(output) {
|
|
2608
|
+
const lines = output.split(`
|
|
2609
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2610
|
+
const messageSegments = [];
|
|
2611
|
+
let currentSegment = "";
|
|
2612
|
+
let hasAgentMessage = false;
|
|
2613
|
+
for (const line of lines) {
|
|
2614
|
+
try {
|
|
2615
|
+
const event = JSON.parse(line);
|
|
2616
|
+
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
2617
|
+
if (isMessageChunk) {
|
|
2618
|
+
hasAgentMessage = true;
|
|
2619
|
+
const chunk = event.params.update.content.text ?? "";
|
|
2620
|
+
if (chunk.length > 0) {
|
|
2621
|
+
currentSegment += chunk;
|
|
2622
|
+
}
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
if (currentSegment.trim().length > 0) {
|
|
2626
|
+
messageSegments.push(currentSegment.trim());
|
|
2627
|
+
}
|
|
2628
|
+
currentSegment = "";
|
|
2629
|
+
} catch {
|
|
2630
|
+
if (currentSegment.trim().length > 0) {
|
|
2631
|
+
messageSegments.push(currentSegment.trim());
|
|
2632
|
+
currentSegment = "";
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (currentSegment.trim().length > 0) {
|
|
2637
|
+
messageSegments.push(currentSegment.trim());
|
|
2638
|
+
}
|
|
2639
|
+
if (messageSegments.length > 0) {
|
|
2640
|
+
return {
|
|
2641
|
+
text: messageSegments[messageSegments.length - 1],
|
|
2642
|
+
hasAgentMessage
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
return {
|
|
2646
|
+
text: output.trim(),
|
|
2647
|
+
hasAgentMessage
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function sanitizePromptText(text) {
|
|
2651
|
+
const trimmed = text.trim();
|
|
2652
|
+
const paragraphs = trimmed.split(/\n\s*\n/);
|
|
2653
|
+
if (paragraphs.length < 2) {
|
|
2654
|
+
return trimmed;
|
|
2655
|
+
}
|
|
2656
|
+
const firstParagraph = paragraphs[0].trim().replace(/\s+/g, " ").toLowerCase();
|
|
2657
|
+
if (!looksLikeWorkflowPreamble(firstParagraph)) {
|
|
2658
|
+
return trimmed;
|
|
2659
|
+
}
|
|
2660
|
+
return paragraphs.slice(1).join(`
|
|
2661
|
+
|
|
2662
|
+
`).trim();
|
|
2663
|
+
}
|
|
2664
|
+
function looksLikeWorkflowPreamble(paragraph) {
|
|
2665
|
+
if (!paragraph.startsWith("using ")) {
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2668
|
+
return paragraph.includes("using-superpowers") || paragraph.includes("repo workflow requirement") || paragraph.includes("workflow requirement") || paragraph.includes("before responding") || paragraph.includes("skill check");
|
|
2669
|
+
}
|
|
2670
|
+
function extractJsonRpcErrorMessages(output) {
|
|
2671
|
+
return output.split(`
|
|
2672
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
|
|
2673
|
+
try {
|
|
2674
|
+
const payload = JSON.parse(line);
|
|
2675
|
+
if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
|
|
2676
|
+
return [payload.error.message];
|
|
2677
|
+
}
|
|
2678
|
+
} catch {
|
|
2679
|
+
return [];
|
|
2680
|
+
}
|
|
2681
|
+
return [];
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2479
2685
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
2480
2686
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
2481
|
-
import { dirname as
|
|
2687
|
+
import { dirname as dirname7, join as join3 } from "node:path";
|
|
2482
2688
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
2483
2689
|
if (platform === "win32") {
|
|
2484
2690
|
return null;
|
|
2485
2691
|
}
|
|
2486
|
-
return join3(
|
|
2692
|
+
return join3(dirname7(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
2487
2693
|
}
|
|
2488
2694
|
async function ensureNodePtyHelperExecutable(helperPath, chmod = chmodFs) {
|
|
2489
2695
|
if (!helperPath) {
|
|
@@ -2505,8 +2711,9 @@ import { createRequire as createRequire3 } from "node:module";
|
|
|
2505
2711
|
import { spawn as spawn3 } from "node:child_process";
|
|
2506
2712
|
import { spawn as spawnPty } from "node-pty";
|
|
2507
2713
|
async function defaultRunner(command, args, options) {
|
|
2508
|
-
return await new Promise((
|
|
2509
|
-
const
|
|
2714
|
+
return await new Promise((resolve, reject) => {
|
|
2715
|
+
const spawnSpec = resolveSpawnCommand(command, args);
|
|
2716
|
+
const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
2510
2717
|
let stdout = "";
|
|
2511
2718
|
let stderr = "";
|
|
2512
2719
|
const timeoutId = options?.timeoutMs ? setTimeout(() => {
|
|
@@ -2527,15 +2734,16 @@ async function defaultRunner(command, args, options) {
|
|
|
2527
2734
|
child.on("close", (code) => {
|
|
2528
2735
|
if (timeoutId)
|
|
2529
2736
|
clearTimeout(timeoutId);
|
|
2530
|
-
|
|
2737
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
2531
2738
|
});
|
|
2532
2739
|
});
|
|
2533
2740
|
}
|
|
2534
2741
|
async function defaultPtyRunner(command, args, options) {
|
|
2535
2742
|
const helperPath = resolveNodePtyHelperPath(require3.resolve("node-pty/package.json"), process.platform, process.arch);
|
|
2536
2743
|
await ensureNodePtyHelperExecutable(helperPath);
|
|
2537
|
-
return await new Promise((
|
|
2538
|
-
const
|
|
2744
|
+
return await new Promise((resolve, reject) => {
|
|
2745
|
+
const spawnSpec = resolveSpawnCommand(command, args);
|
|
2746
|
+
const child = spawnPty(spawnSpec.command, spawnSpec.args, {
|
|
2539
2747
|
name: "xterm-color",
|
|
2540
2748
|
cols: 80,
|
|
2541
2749
|
rows: 24,
|
|
@@ -2553,7 +2761,7 @@ async function defaultPtyRunner(command, args, options) {
|
|
|
2553
2761
|
child.onExit(({ exitCode }) => {
|
|
2554
2762
|
if (timeoutId)
|
|
2555
2763
|
clearTimeout(timeoutId);
|
|
2556
|
-
|
|
2764
|
+
resolve({ code: exitCode, stdout: output, stderr: "" });
|
|
2557
2765
|
});
|
|
2558
2766
|
});
|
|
2559
2767
|
}
|
|
@@ -2582,8 +2790,8 @@ class AcpxCliTransport {
|
|
|
2582
2790
|
});
|
|
2583
2791
|
}
|
|
2584
2792
|
async prompt(session, text) {
|
|
2585
|
-
const
|
|
2586
|
-
return { text:
|
|
2793
|
+
const result = await this.runCommand(this.command, this.buildPromptArgs(session, text));
|
|
2794
|
+
return { text: getPromptText(result) };
|
|
2587
2795
|
}
|
|
2588
2796
|
async cancel(session) {
|
|
2589
2797
|
const output = await this.run(this.buildArgs(session, [
|
|
@@ -2624,12 +2832,13 @@ class AcpxCliTransport {
|
|
|
2624
2832
|
return result.stdout;
|
|
2625
2833
|
}
|
|
2626
2834
|
async runCommandWithTimeout(runner, args, options) {
|
|
2835
|
+
const spawnSpec = resolveSpawnCommand(this.command, args);
|
|
2627
2836
|
if (!options?.timeoutMs) {
|
|
2628
|
-
return await runner(
|
|
2837
|
+
return await runner(spawnSpec.command, spawnSpec.args, undefined);
|
|
2629
2838
|
}
|
|
2630
2839
|
let timeoutId;
|
|
2631
2840
|
return await Promise.race([
|
|
2632
|
-
runner(
|
|
2841
|
+
runner(spawnSpec.command, spawnSpec.args, options).finally(() => {
|
|
2633
2842
|
if (timeoutId)
|
|
2634
2843
|
clearTimeout(timeoutId);
|
|
2635
2844
|
}),
|
|
@@ -2672,88 +2881,9 @@ function renderCommandForError(args) {
|
|
|
2672
2881
|
}
|
|
2673
2882
|
return rendered.join(" ");
|
|
2674
2883
|
}
|
|
2675
|
-
function extractPromptText(output) {
|
|
2676
|
-
const lines = output.split(`
|
|
2677
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2678
|
-
const messageSegments = [];
|
|
2679
|
-
let currentSegment = "";
|
|
2680
|
-
for (const line of lines) {
|
|
2681
|
-
try {
|
|
2682
|
-
const event = JSON.parse(line);
|
|
2683
|
-
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
2684
|
-
if (isMessageChunk) {
|
|
2685
|
-
const chunk = event.params.update.content.text ?? "";
|
|
2686
|
-
if (chunk.length > 0) {
|
|
2687
|
-
currentSegment += chunk;
|
|
2688
|
-
}
|
|
2689
|
-
continue;
|
|
2690
|
-
}
|
|
2691
|
-
if (currentSegment.trim().length > 0) {
|
|
2692
|
-
messageSegments.push(currentSegment.trim());
|
|
2693
|
-
}
|
|
2694
|
-
currentSegment = "";
|
|
2695
|
-
} catch {
|
|
2696
|
-
if (currentSegment.trim().length > 0) {
|
|
2697
|
-
messageSegments.push(currentSegment.trim());
|
|
2698
|
-
currentSegment = "";
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
if (currentSegment.trim().length > 0) {
|
|
2703
|
-
messageSegments.push(currentSegment.trim());
|
|
2704
|
-
}
|
|
2705
|
-
if (messageSegments.length > 0) {
|
|
2706
|
-
return messageSegments[messageSegments.length - 1];
|
|
2707
|
-
}
|
|
2708
|
-
return output.trim();
|
|
2709
|
-
}
|
|
2710
|
-
function sanitizePromptText(text) {
|
|
2711
|
-
const trimmed = text.trim();
|
|
2712
|
-
const paragraphs = trimmed.split(/\n\s*\n/);
|
|
2713
|
-
if (paragraphs.length < 2) {
|
|
2714
|
-
return trimmed;
|
|
2715
|
-
}
|
|
2716
|
-
const firstParagraph = paragraphs[0].trim().replace(/\s+/g, " ").toLowerCase();
|
|
2717
|
-
if (!looksLikeWorkflowPreamble(firstParagraph)) {
|
|
2718
|
-
return trimmed;
|
|
2719
|
-
}
|
|
2720
|
-
return paragraphs.slice(1).join(`
|
|
2721
|
-
|
|
2722
|
-
`).trim();
|
|
2723
|
-
}
|
|
2724
|
-
function looksLikeWorkflowPreamble(paragraph) {
|
|
2725
|
-
if (!paragraph.startsWith("using ")) {
|
|
2726
|
-
return false;
|
|
2727
|
-
}
|
|
2728
|
-
return paragraph.includes("using-superpowers") || paragraph.includes("repo workflow requirement") || paragraph.includes("workflow requirement") || paragraph.includes("before responding") || paragraph.includes("skill check");
|
|
2729
|
-
}
|
|
2730
|
-
function normalizeCommandError(result) {
|
|
2731
|
-
const rpcMessages = extractJsonRpcErrorMessages(result.stderr).concat(extractJsonRpcErrorMessages(result.stdout)).filter((message) => message.length > 0);
|
|
2732
|
-
const preferredMessage = [...rpcMessages].reverse().find((message) => message !== "Resource not found");
|
|
2733
|
-
if (preferredMessage) {
|
|
2734
|
-
return preferredMessage;
|
|
2735
|
-
}
|
|
2736
|
-
if (rpcMessages.length > 0) {
|
|
2737
|
-
return rpcMessages[rpcMessages.length - 1] ?? null;
|
|
2738
|
-
}
|
|
2739
|
-
return result.stderr.trim() || result.stdout.trim() || null;
|
|
2740
|
-
}
|
|
2741
|
-
function extractJsonRpcErrorMessages(output) {
|
|
2742
|
-
return output.split(`
|
|
2743
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
|
|
2744
|
-
try {
|
|
2745
|
-
const payload = JSON.parse(line);
|
|
2746
|
-
if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
|
|
2747
|
-
return [payload.error.message];
|
|
2748
|
-
}
|
|
2749
|
-
} catch {
|
|
2750
|
-
return [];
|
|
2751
|
-
}
|
|
2752
|
-
return [];
|
|
2753
|
-
});
|
|
2754
|
-
}
|
|
2755
2884
|
var require3;
|
|
2756
2885
|
var init_acpx_cli_transport = __esm(() => {
|
|
2886
|
+
init_spawn_command();
|
|
2757
2887
|
init_node_pty_helper();
|
|
2758
2888
|
require3 = createRequire3(import.meta.url);
|
|
2759
2889
|
});
|
|
@@ -2765,8 +2895,8 @@ __export(exports_main, {
|
|
|
2765
2895
|
main: () => main2,
|
|
2766
2896
|
buildApp: () => buildApp
|
|
2767
2897
|
});
|
|
2768
|
-
import { homedir } from "node:os";
|
|
2769
|
-
import { dirname as
|
|
2898
|
+
import { homedir as homedir2 } from "node:os";
|
|
2899
|
+
import { dirname as dirname8, join as join4 } from "node:path";
|
|
2770
2900
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2771
2901
|
async function buildApp(paths, deps = {}) {
|
|
2772
2902
|
await ensureConfigExists(paths.configPath);
|
|
@@ -2826,7 +2956,7 @@ async function main2() {
|
|
|
2826
2956
|
}
|
|
2827
2957
|
}
|
|
2828
2958
|
function resolveRuntimePaths() {
|
|
2829
|
-
const home = process.env.HOME ??
|
|
2959
|
+
const home = process.env.HOME ?? homedir2();
|
|
2830
2960
|
if (!home) {
|
|
2831
2961
|
throw new Error("Unable to resolve the current user home directory");
|
|
2832
2962
|
}
|
|
@@ -2842,7 +2972,7 @@ function resolveBridgeEntryPath() {
|
|
|
2842
2972
|
return fileURLToPath2(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
2843
2973
|
}
|
|
2844
2974
|
function resolveAppLogPath(configPath) {
|
|
2845
|
-
const rootDir =
|
|
2975
|
+
const rootDir = dirname8(configPath);
|
|
2846
2976
|
const runtimeDir = join4(rootDir, "runtime");
|
|
2847
2977
|
return join4(runtimeDir, "app.log");
|
|
2848
2978
|
}
|
|
@@ -2862,7 +2992,7 @@ var init_main = __esm(async () => {
|
|
|
2862
2992
|
});
|
|
2863
2993
|
|
|
2864
2994
|
// src/cli.ts
|
|
2865
|
-
import { homedir as
|
|
2995
|
+
import { homedir as homedir3 } from "node:os";
|
|
2866
2996
|
import { sep } from "node:path";
|
|
2867
2997
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2868
2998
|
|
|
@@ -3107,6 +3237,7 @@ async function spawnWindowsHiddenProcess(request) {
|
|
|
3107
3237
|
return;
|
|
3108
3238
|
}
|
|
3109
3239
|
settled = true;
|
|
3240
|
+
child.stdout?.destroy();
|
|
3110
3241
|
child.unref();
|
|
3111
3242
|
resolve(pid);
|
|
3112
3243
|
});
|
|
@@ -3322,7 +3453,7 @@ function createDefaultController() {
|
|
|
3322
3453
|
});
|
|
3323
3454
|
}
|
|
3324
3455
|
function requireHome() {
|
|
3325
|
-
const home = process.env.HOME ??
|
|
3456
|
+
const home = process.env.HOME ?? homedir3();
|
|
3326
3457
|
if (!home) {
|
|
3327
3458
|
throw new Error("Unable to resolve the current user home directory");
|
|
3328
3459
|
}
|