weacpx 0.1.7 → 0.2.0
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 +1 -0
- package/config.example.json +4 -1
- package/dist/bridge/bridge-main.js +179 -10
- package/dist/cli.js +1048 -166
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,129 @@ var __export = (target, all) => {
|
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
49
|
|
|
50
|
+
// src/weixin/monitor/consumer-lock.ts
|
|
51
|
+
import { mkdir as mkdir5, open as open2, readFile as readFile3, rm as rm4 } from "node:fs/promises";
|
|
52
|
+
import { dirname as dirname4, join as join2 } from "node:path";
|
|
53
|
+
import { homedir } from "node:os";
|
|
54
|
+
function createWeixinConsumerLock(options = {}) {
|
|
55
|
+
const lockFilePath = options.lockFilePath ?? join2(homedir(), ".weacpx", "runtime", "weixin-consumer.lock.json");
|
|
56
|
+
const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning2;
|
|
57
|
+
const onDiagnostic = options.onDiagnostic;
|
|
58
|
+
return {
|
|
59
|
+
async acquire(meta) {
|
|
60
|
+
await mkdir5(dirname4(lockFilePath), { recursive: true });
|
|
61
|
+
while (true) {
|
|
62
|
+
try {
|
|
63
|
+
const handle = await open2(lockFilePath, "wx");
|
|
64
|
+
try {
|
|
65
|
+
await handle.writeFile(`${JSON.stringify(meta, null, 2)}
|
|
66
|
+
`, "utf8");
|
|
67
|
+
} finally {
|
|
68
|
+
await handle.close();
|
|
69
|
+
}
|
|
70
|
+
await onDiagnostic?.("lock_acquired", {
|
|
71
|
+
lockFilePath,
|
|
72
|
+
pid: meta.pid,
|
|
73
|
+
mode: meta.mode,
|
|
74
|
+
configPath: meta.configPath,
|
|
75
|
+
statePath: meta.statePath,
|
|
76
|
+
hostname: meta.hostname
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const code = error.code;
|
|
81
|
+
if (code !== "EEXIST") {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
await onDiagnostic?.("lock_exists", {
|
|
85
|
+
lockFilePath,
|
|
86
|
+
pid: meta.pid,
|
|
87
|
+
mode: meta.mode
|
|
88
|
+
});
|
|
89
|
+
const existing = await loadLockMetadata(lockFilePath);
|
|
90
|
+
if (!existing) {
|
|
91
|
+
await rm4(lockFilePath, { force: true });
|
|
92
|
+
await onDiagnostic?.("lock_invalid_removed", {
|
|
93
|
+
lockFilePath,
|
|
94
|
+
reason: "invalid_or_unreadable_metadata"
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isProcessRunning(existing.pid)) {
|
|
99
|
+
await rm4(lockFilePath, { force: true });
|
|
100
|
+
await onDiagnostic?.("lock_stale_removed", {
|
|
101
|
+
lockFilePath,
|
|
102
|
+
stalePid: existing.pid,
|
|
103
|
+
staleMode: existing.mode,
|
|
104
|
+
staleConfigPath: existing.configPath,
|
|
105
|
+
staleStatePath: existing.statePath,
|
|
106
|
+
reason: "owner_process_not_running"
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
await onDiagnostic?.("lock_active_conflict", {
|
|
111
|
+
lockFilePath,
|
|
112
|
+
activePid: existing.pid,
|
|
113
|
+
activeMode: existing.mode,
|
|
114
|
+
activeConfigPath: existing.configPath,
|
|
115
|
+
activeStatePath: existing.statePath,
|
|
116
|
+
requestedPid: meta.pid,
|
|
117
|
+
requestedMode: meta.mode
|
|
118
|
+
});
|
|
119
|
+
throw new ActiveWeixinConsumerLockError(lockFilePath, existing);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
async release() {
|
|
124
|
+
await rm4(lockFilePath, { force: true });
|
|
125
|
+
await onDiagnostic?.("lock_released", {
|
|
126
|
+
lockFilePath
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function loadLockMetadata(path) {
|
|
132
|
+
try {
|
|
133
|
+
const raw = await readFile3(path, "utf8");
|
|
134
|
+
const parsed = JSON.parse(raw);
|
|
135
|
+
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function defaultIsProcessRunning2(pid) {
|
|
144
|
+
try {
|
|
145
|
+
process.kill(pid, 0);
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
var ActiveWeixinConsumerLockError;
|
|
152
|
+
var init_consumer_lock = __esm(() => {
|
|
153
|
+
ActiveWeixinConsumerLockError = class ActiveWeixinConsumerLockError extends Error {
|
|
154
|
+
existing;
|
|
155
|
+
lockFilePath;
|
|
156
|
+
constructor(lockFilePath, existing) {
|
|
157
|
+
super([
|
|
158
|
+
"weacpx Weixin consumer is already running.",
|
|
159
|
+
`pid: ${existing.pid}`,
|
|
160
|
+
`mode: ${existing.mode}`,
|
|
161
|
+
`config: ${existing.configPath}`,
|
|
162
|
+
`state: ${existing.statePath}`,
|
|
163
|
+
"Try stopping the existing instance or close the foreground `weacpx run` process before starting a new one."
|
|
164
|
+
].join(`
|
|
165
|
+
`));
|
|
166
|
+
this.name = "ActiveWeixinConsumerLockError";
|
|
167
|
+
this.lockFilePath = lockFilePath;
|
|
168
|
+
this.existing = existing;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
50
173
|
// node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
|
|
51
174
|
var require_QRMode = __commonJS((exports, module) => {
|
|
52
175
|
module.exports = {
|
|
@@ -2428,6 +2551,25 @@ var init_media_download = __esm(() => {
|
|
|
2428
2551
|
WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
|
|
2429
2552
|
});
|
|
2430
2553
|
|
|
2554
|
+
// src/weixin/messaging/execute-chat-turn.ts
|
|
2555
|
+
async function executeChatTurn(params) {
|
|
2556
|
+
let usedReply = false;
|
|
2557
|
+
const response = await params.agent.chat({
|
|
2558
|
+
...params.request,
|
|
2559
|
+
reply: async (text) => {
|
|
2560
|
+
const delivered = await params.onReplySegment?.(text);
|
|
2561
|
+
if (delivered !== false) {
|
|
2562
|
+
usedReply = true;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
return {
|
|
2567
|
+
text: usedReply ? undefined : response.text,
|
|
2568
|
+
media: response.media,
|
|
2569
|
+
usedReply
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2431
2573
|
// src/weixin/messaging/inbound.ts
|
|
2432
2574
|
function contextTokenKey(accountId, userId) {
|
|
2433
2575
|
return `${accountId}:${userId}`;
|
|
@@ -2826,7 +2968,7 @@ var init_slash_commands = __esm(() => {
|
|
|
2826
2968
|
init_send();
|
|
2827
2969
|
});
|
|
2828
2970
|
|
|
2829
|
-
// src/weixin/messaging/
|
|
2971
|
+
// src/weixin/messaging/handle-weixin-message-turn.ts
|
|
2830
2972
|
import crypto4 from "node:crypto";
|
|
2831
2973
|
import fs6 from "node:fs/promises";
|
|
2832
2974
|
import { tmpdir } from "node:os";
|
|
@@ -2869,7 +3011,7 @@ function findMediaItem(itemList) {
|
|
|
2869
3011
|
const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item && isMediaItem(item.ref_msg.message_item));
|
|
2870
3012
|
return refItem?.ref_msg?.message_item ?? undefined;
|
|
2871
3013
|
}
|
|
2872
|
-
async function
|
|
3014
|
+
async function handleWeixinMessageTurn(full, deps) {
|
|
2873
3015
|
const receivedAt = Date.now();
|
|
2874
3016
|
const textBody = extractTextBody(full.item_list);
|
|
2875
3017
|
if (textBody.startsWith("/")) {
|
|
@@ -2924,22 +3066,27 @@ async function processOneMessage(full, deps) {
|
|
|
2924
3066
|
}
|
|
2925
3067
|
}
|
|
2926
3068
|
const to = full.from_user_id ?? "";
|
|
2927
|
-
const
|
|
3069
|
+
const sendReplySegment = async (text) => {
|
|
3070
|
+
const plainText = markdownToPlainText(text).trim();
|
|
3071
|
+
if (plainText.length === 0) {
|
|
3072
|
+
return false;
|
|
3073
|
+
}
|
|
2928
3074
|
try {
|
|
2929
3075
|
await sendMessageWeixin({
|
|
2930
3076
|
to,
|
|
2931
|
-
text:
|
|
3077
|
+
text: plainText,
|
|
2932
3078
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
2933
3079
|
});
|
|
3080
|
+
return true;
|
|
2934
3081
|
} catch (err) {
|
|
2935
3082
|
deps.errLog(`intermediate reply failed: ${String(err)}`);
|
|
3083
|
+
return false;
|
|
2936
3084
|
}
|
|
2937
3085
|
};
|
|
2938
3086
|
const request = {
|
|
2939
3087
|
conversationId: full.from_user_id ?? "",
|
|
2940
3088
|
text: bodyFromItemList(full.item_list),
|
|
2941
|
-
media
|
|
2942
|
-
reply
|
|
3089
|
+
media
|
|
2943
3090
|
};
|
|
2944
3091
|
let typingTimer;
|
|
2945
3092
|
const startTyping = () => {
|
|
@@ -2960,10 +3107,14 @@ async function processOneMessage(full, deps) {
|
|
|
2960
3107
|
typingTimer = setInterval(startTyping, 1e4);
|
|
2961
3108
|
}
|
|
2962
3109
|
try {
|
|
2963
|
-
const
|
|
2964
|
-
|
|
3110
|
+
const turn = await executeChatTurn({
|
|
3111
|
+
agent: deps.agent,
|
|
3112
|
+
request,
|
|
3113
|
+
onReplySegment: sendReplySegment
|
|
3114
|
+
});
|
|
3115
|
+
if (turn.media) {
|
|
2965
3116
|
let filePath;
|
|
2966
|
-
const mediaUrl =
|
|
3117
|
+
const mediaUrl = turn.media.url;
|
|
2967
3118
|
if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
|
|
2968
3119
|
filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(resolveMediaTempDir(deps.mediaTempDir), "outbound"));
|
|
2969
3120
|
} else {
|
|
@@ -2972,20 +3123,24 @@ async function processOneMessage(full, deps) {
|
|
|
2972
3123
|
await sendWeixinMediaFile({
|
|
2973
3124
|
filePath,
|
|
2974
3125
|
to,
|
|
2975
|
-
text:
|
|
3126
|
+
text: turn.text ? markdownToPlainText(turn.text) : "",
|
|
2976
3127
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken },
|
|
2977
3128
|
cdnBaseUrl: deps.cdnBaseUrl
|
|
2978
3129
|
});
|
|
2979
|
-
} else if (
|
|
3130
|
+
} else if (turn.text) {
|
|
3131
|
+
const finalText = markdownToPlainText(turn.text).trim();
|
|
3132
|
+
if (finalText.length === 0) {
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
2980
3135
|
await sendMessageWeixin({
|
|
2981
3136
|
to,
|
|
2982
|
-
text:
|
|
3137
|
+
text: finalText,
|
|
2983
3138
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
2984
3139
|
});
|
|
2985
3140
|
}
|
|
2986
3141
|
} catch (err) {
|
|
2987
3142
|
const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
|
|
2988
|
-
deps.errLog(`
|
|
3143
|
+
deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
|
|
2989
3144
|
sendWeixinErrorNotice({
|
|
2990
3145
|
to,
|
|
2991
3146
|
contextToken,
|
|
@@ -3011,7 +3166,7 @@ async function processOneMessage(full, deps) {
|
|
|
3011
3166
|
}
|
|
3012
3167
|
}
|
|
3013
3168
|
var hasDownloadableMedia = (media) => media?.encrypt_query_param || media?.full_url;
|
|
3014
|
-
var
|
|
3169
|
+
var init_handle_weixin_message_turn = __esm(() => {
|
|
3015
3170
|
init_api();
|
|
3016
3171
|
init_types();
|
|
3017
3172
|
init_upload();
|
|
@@ -3144,7 +3299,7 @@ async function monitorWeixinProvider(opts) {
|
|
|
3144
3299
|
aLog.info(`inbound: from=${full.from_user_id} types=${full.item_list?.map((i) => i.type).join(",") ?? "none"}`);
|
|
3145
3300
|
const fromUserId = full.from_user_id ?? "";
|
|
3146
3301
|
const cachedConfig = await configManager.getForUser(fromUserId, full.context_token);
|
|
3147
|
-
await
|
|
3302
|
+
await handleWeixinMessageTurn(full, {
|
|
3148
3303
|
accountId,
|
|
3149
3304
|
agent,
|
|
3150
3305
|
baseUrl,
|
|
@@ -3186,7 +3341,7 @@ var init_monitor = __esm(() => {
|
|
|
3186
3341
|
init_api();
|
|
3187
3342
|
init_config_cache();
|
|
3188
3343
|
init_session_guard();
|
|
3189
|
-
|
|
3344
|
+
init_handle_weixin_message_turn();
|
|
3190
3345
|
init_sync_buf();
|
|
3191
3346
|
init_logger();
|
|
3192
3347
|
});
|
|
@@ -3334,8 +3489,8 @@ var init_weixin_sdk = __esm(() => {
|
|
|
3334
3489
|
});
|
|
3335
3490
|
|
|
3336
3491
|
// src/logging/app-logger.ts
|
|
3337
|
-
import { appendFile, mkdir as
|
|
3338
|
-
import { basename, dirname as
|
|
3492
|
+
import { appendFile, mkdir as mkdir6, readdir, rename, rm as rm5, stat } from "node:fs/promises";
|
|
3493
|
+
import { basename, dirname as dirname5, join as join3 } from "node:path";
|
|
3339
3494
|
function createNoopAppLogger() {
|
|
3340
3495
|
return {
|
|
3341
3496
|
debug: async () => {},
|
|
@@ -3365,7 +3520,7 @@ function createAppLogger(options) {
|
|
|
3365
3520
|
return;
|
|
3366
3521
|
}
|
|
3367
3522
|
const line = formatLogLine(now(), level, event, message, context);
|
|
3368
|
-
await
|
|
3523
|
+
await mkdir6(dirname5(options.filePath), { recursive: true });
|
|
3369
3524
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
3370
3525
|
await appendFile(options.filePath, line, "utf8");
|
|
3371
3526
|
}
|
|
@@ -3386,10 +3541,10 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
|
3386
3541
|
return;
|
|
3387
3542
|
}
|
|
3388
3543
|
if (maxFiles <= 0) {
|
|
3389
|
-
await
|
|
3544
|
+
await rm5(filePath, { force: true });
|
|
3390
3545
|
return;
|
|
3391
3546
|
}
|
|
3392
|
-
await
|
|
3547
|
+
await rm5(`${filePath}.${maxFiles}`, { force: true });
|
|
3393
3548
|
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
3394
3549
|
const source = `${filePath}.${index}`;
|
|
3395
3550
|
try {
|
|
@@ -3403,7 +3558,7 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
|
3403
3558
|
await rename(filePath, `${filePath}.1`);
|
|
3404
3559
|
}
|
|
3405
3560
|
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
3406
|
-
const parentDir =
|
|
3561
|
+
const parentDir = dirname5(filePath);
|
|
3407
3562
|
const prefix = `${basename(filePath)}.`;
|
|
3408
3563
|
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
3409
3564
|
let files = [];
|
|
@@ -3419,10 +3574,10 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
|
3419
3574
|
if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
|
|
3420
3575
|
continue;
|
|
3421
3576
|
}
|
|
3422
|
-
const candidate =
|
|
3577
|
+
const candidate = join3(parentDir, file);
|
|
3423
3578
|
const details = await stat(candidate);
|
|
3424
3579
|
if (details.mtime.getTime() < cutoff) {
|
|
3425
|
-
await
|
|
3580
|
+
await rm5(candidate, { force: true });
|
|
3426
3581
|
}
|
|
3427
3582
|
}
|
|
3428
3583
|
}
|
|
@@ -3454,16 +3609,16 @@ var init_app_logger = __esm(() => {
|
|
|
3454
3609
|
});
|
|
3455
3610
|
|
|
3456
3611
|
// src/transport/acpx-session-index.ts
|
|
3457
|
-
import { readFile as
|
|
3458
|
-
import { homedir } from "node:os";
|
|
3612
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
3613
|
+
import { homedir as homedir2 } from "node:os";
|
|
3459
3614
|
import { resolve } from "node:path";
|
|
3460
3615
|
async function resolveSessionAgentCommandFromIndex(session) {
|
|
3461
|
-
const home = process.env.HOME ??
|
|
3616
|
+
const home = process.env.HOME ?? homedir2();
|
|
3462
3617
|
if (!home) {
|
|
3463
3618
|
return;
|
|
3464
3619
|
}
|
|
3465
3620
|
try {
|
|
3466
|
-
const raw = await
|
|
3621
|
+
const raw = await readFile4(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
|
|
3467
3622
|
const parsed = JSON.parse(raw);
|
|
3468
3623
|
const targetCwd = resolve(session.cwd);
|
|
3469
3624
|
const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
|
|
@@ -3614,8 +3769,10 @@ function parseCommand(input) {
|
|
|
3614
3769
|
}
|
|
3615
3770
|
const parts = tokenizeCommand(trimmed);
|
|
3616
3771
|
const command = normalizeCommand(parts[0] ?? "");
|
|
3617
|
-
if (command === "/help")
|
|
3772
|
+
if (command === "/help" && parts.length === 1)
|
|
3618
3773
|
return { kind: "help" };
|
|
3774
|
+
if (command === "/help" && parts.length === 2)
|
|
3775
|
+
return { kind: "help", topic: parts[1] };
|
|
3619
3776
|
if (command === "/agents")
|
|
3620
3777
|
return { kind: "agents" };
|
|
3621
3778
|
if (command === "/workspaces")
|
|
@@ -3630,6 +3787,10 @@ function parseCommand(input) {
|
|
|
3630
3787
|
return { kind: "session.reset" };
|
|
3631
3788
|
if (command === "/mode" && parts.length === 1)
|
|
3632
3789
|
return { kind: "mode.show" };
|
|
3790
|
+
if (command === "/replymode" && parts.length === 1)
|
|
3791
|
+
return { kind: "replymode.show" };
|
|
3792
|
+
if (command === "/config" && parts.length === 1)
|
|
3793
|
+
return { kind: "config.show" };
|
|
3633
3794
|
if (command === "/permission" && parts.length === 1)
|
|
3634
3795
|
return { kind: "permission.status" };
|
|
3635
3796
|
if (command === "/session" && parts.length === 1)
|
|
@@ -3653,12 +3814,21 @@ function parseCommand(input) {
|
|
|
3653
3814
|
return { kind: "permission.auto.set", policy };
|
|
3654
3815
|
}
|
|
3655
3816
|
}
|
|
3817
|
+
if (command === "/config" && parts[1] === "set" && parts.length === 4) {
|
|
3818
|
+
return { kind: "config.set", path: parts[2] ?? "", value: parts[3] ?? "" };
|
|
3819
|
+
}
|
|
3656
3820
|
if (command === "/use" && parts[1]) {
|
|
3657
3821
|
return { kind: "session.use", alias: parts[1] };
|
|
3658
3822
|
}
|
|
3659
3823
|
if (command === "/mode" && parts[1]) {
|
|
3660
3824
|
return { kind: "mode.set", modeId: parts[1] };
|
|
3661
3825
|
}
|
|
3826
|
+
if (command === "/replymode" && parts[1] === "reset" && parts.length === 2) {
|
|
3827
|
+
return { kind: "replymode.reset" };
|
|
3828
|
+
}
|
|
3829
|
+
if (command === "/replymode" && (parts[1] === "stream" || parts[1] === "final") && parts.length === 2) {
|
|
3830
|
+
return { kind: "replymode.set", replyMode: parts[1] };
|
|
3831
|
+
}
|
|
3662
3832
|
if (command === "/agent" && parts[1] === "add" && parts[2]) {
|
|
3663
3833
|
return { kind: "agent.add", template: parts[2] };
|
|
3664
3834
|
}
|
|
@@ -3668,13 +3838,23 @@ function parseCommand(input) {
|
|
|
3668
3838
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
3669
3839
|
const name = parts[2];
|
|
3670
3840
|
let cwd = "";
|
|
3841
|
+
let invalid = false;
|
|
3671
3842
|
for (let index = 3;index < parts.length; index += 1) {
|
|
3672
3843
|
if (parts[index] === "--cwd" || parts[index] === "-d") {
|
|
3844
|
+
if (index + 1 >= parts.length) {
|
|
3845
|
+
invalid = true;
|
|
3846
|
+
break;
|
|
3847
|
+
}
|
|
3673
3848
|
cwd = parts[index + 1] ?? "";
|
|
3674
3849
|
index += 1;
|
|
3850
|
+
continue;
|
|
3675
3851
|
}
|
|
3852
|
+
invalid = true;
|
|
3853
|
+
break;
|
|
3854
|
+
}
|
|
3855
|
+
if (!invalid && name.trim().length > 0 && cwd.trim().length > 0) {
|
|
3856
|
+
return { kind: "workspace.new", name, cwd };
|
|
3676
3857
|
}
|
|
3677
|
-
return { kind: "workspace.new", name, cwd };
|
|
3678
3858
|
}
|
|
3679
3859
|
if (command === "/workspace" && parts[1] === "rm" && parts[2]) {
|
|
3680
3860
|
return { kind: "workspace.rm", name: parts[2] };
|
|
@@ -3684,26 +3864,41 @@ function parseCommand(input) {
|
|
|
3684
3864
|
const alias = parts[2];
|
|
3685
3865
|
let agent = "";
|
|
3686
3866
|
let workspace = "";
|
|
3867
|
+
let invalid = false;
|
|
3687
3868
|
for (let index = 3;index < parts.length; index += 1) {
|
|
3688
3869
|
if (parts[index] === "--agent" || parts[index] === "-a") {
|
|
3870
|
+
if (index + 1 >= parts.length) {
|
|
3871
|
+
invalid = true;
|
|
3872
|
+
break;
|
|
3873
|
+
}
|
|
3689
3874
|
agent = parts[index + 1] ?? "";
|
|
3690
3875
|
index += 1;
|
|
3876
|
+
continue;
|
|
3691
3877
|
} else if (parts[index] === "--ws" || parts[index] === "-ws") {
|
|
3878
|
+
if (index + 1 >= parts.length) {
|
|
3879
|
+
invalid = true;
|
|
3880
|
+
break;
|
|
3881
|
+
}
|
|
3692
3882
|
workspace = parts[index + 1] ?? "";
|
|
3693
3883
|
index += 1;
|
|
3884
|
+
continue;
|
|
3694
3885
|
}
|
|
3886
|
+
invalid = true;
|
|
3887
|
+
break;
|
|
3888
|
+
}
|
|
3889
|
+
if (!invalid && alias.trim().length > 0 && agent.trim().length > 0 && workspace.trim().length > 0) {
|
|
3890
|
+
return { kind: "session.new", alias, agent, workspace };
|
|
3695
3891
|
}
|
|
3696
|
-
return { kind: "session.new", alias, agent, workspace };
|
|
3697
3892
|
}
|
|
3698
|
-
const
|
|
3699
|
-
if (
|
|
3700
|
-
return { kind: "session.shortcut.new", agent: parts[2],
|
|
3893
|
+
const shortcutTarget = readSessionShortcutTarget(parts, 3);
|
|
3894
|
+
if (shortcutTarget) {
|
|
3895
|
+
return { kind: "session.shortcut.new", agent: parts[2], ...shortcutTarget };
|
|
3701
3896
|
}
|
|
3702
3897
|
}
|
|
3703
3898
|
if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach" && parts[1] !== "reset") {
|
|
3704
|
-
const
|
|
3705
|
-
if (
|
|
3706
|
-
return { kind: "session.shortcut", agent: parts[1],
|
|
3899
|
+
const shortcutTarget = readSessionShortcutTarget(parts, 2);
|
|
3900
|
+
if (shortcutTarget) {
|
|
3901
|
+
return { kind: "session.shortcut", agent: parts[1], ...shortcutTarget };
|
|
3707
3902
|
}
|
|
3708
3903
|
}
|
|
3709
3904
|
if (command === "/session" && parts[1] === "attach" && parts[2]) {
|
|
@@ -3711,19 +3906,39 @@ function parseCommand(input) {
|
|
|
3711
3906
|
let agent = "";
|
|
3712
3907
|
let workspace = "";
|
|
3713
3908
|
let transportSession = "";
|
|
3909
|
+
let invalid = false;
|
|
3714
3910
|
for (let index = 3;index < parts.length; index += 1) {
|
|
3715
3911
|
if (parts[index] === "--agent" || parts[index] === "-a") {
|
|
3912
|
+
if (index + 1 >= parts.length) {
|
|
3913
|
+
invalid = true;
|
|
3914
|
+
break;
|
|
3915
|
+
}
|
|
3716
3916
|
agent = parts[index + 1] ?? "";
|
|
3717
3917
|
index += 1;
|
|
3918
|
+
continue;
|
|
3718
3919
|
} else if (parts[index] === "--ws" || parts[index] === "-ws") {
|
|
3920
|
+
if (index + 1 >= parts.length) {
|
|
3921
|
+
invalid = true;
|
|
3922
|
+
break;
|
|
3923
|
+
}
|
|
3719
3924
|
workspace = parts[index + 1] ?? "";
|
|
3720
3925
|
index += 1;
|
|
3926
|
+
continue;
|
|
3721
3927
|
} else if (parts[index] === "--name") {
|
|
3928
|
+
if (index + 1 >= parts.length) {
|
|
3929
|
+
invalid = true;
|
|
3930
|
+
break;
|
|
3931
|
+
}
|
|
3722
3932
|
transportSession = parts[index + 1] ?? "";
|
|
3723
3933
|
index += 1;
|
|
3934
|
+
continue;
|
|
3724
3935
|
}
|
|
3936
|
+
invalid = true;
|
|
3937
|
+
break;
|
|
3938
|
+
}
|
|
3939
|
+
if (!invalid && alias.trim().length > 0 && agent.trim().length > 0 && workspace.trim().length > 0 && transportSession.trim().length > 0) {
|
|
3940
|
+
return { kind: "session.attach", alias, agent, workspace, transportSession };
|
|
3725
3941
|
}
|
|
3726
|
-
return { kind: "session.attach", alias, agent, workspace, transportSession };
|
|
3727
3942
|
}
|
|
3728
3943
|
if (command.startsWith("/") && isRecognizedCommand(command)) {
|
|
3729
3944
|
return { kind: "invalid", text: trimmed, recognizedCommand: command };
|
|
@@ -3733,13 +3948,42 @@ function parseCommand(input) {
|
|
|
3733
3948
|
function hasAnyFlag(parts, flags) {
|
|
3734
3949
|
return parts.some((part) => flags.includes(part));
|
|
3735
3950
|
}
|
|
3736
|
-
function
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3951
|
+
function readSessionShortcutTarget(parts, startIndex) {
|
|
3952
|
+
let cwd = "";
|
|
3953
|
+
let workspace = "";
|
|
3954
|
+
let invalid = false;
|
|
3955
|
+
for (let index = startIndex;index < parts.length; index += 1) {
|
|
3956
|
+
if (parts[index] === "--cwd" || parts[index] === "-d") {
|
|
3957
|
+
if (index + 1 >= parts.length || workspace) {
|
|
3958
|
+
invalid = true;
|
|
3959
|
+
break;
|
|
3960
|
+
}
|
|
3961
|
+
cwd = parts[index + 1] ?? "";
|
|
3962
|
+
index += 1;
|
|
3963
|
+
continue;
|
|
3740
3964
|
}
|
|
3965
|
+
if (parts[index] === "--ws" || parts[index] === "-ws") {
|
|
3966
|
+
if (index + 1 >= parts.length || cwd) {
|
|
3967
|
+
invalid = true;
|
|
3968
|
+
break;
|
|
3969
|
+
}
|
|
3970
|
+
workspace = parts[index + 1] ?? "";
|
|
3971
|
+
index += 1;
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
invalid = true;
|
|
3975
|
+
break;
|
|
3741
3976
|
}
|
|
3742
|
-
|
|
3977
|
+
if (invalid) {
|
|
3978
|
+
return null;
|
|
3979
|
+
}
|
|
3980
|
+
if (cwd.trim().length > 0) {
|
|
3981
|
+
return { cwd };
|
|
3982
|
+
}
|
|
3983
|
+
if (workspace.trim().length > 0) {
|
|
3984
|
+
return { workspace };
|
|
3985
|
+
}
|
|
3986
|
+
return null;
|
|
3743
3987
|
}
|
|
3744
3988
|
function normalizeCommand(command) {
|
|
3745
3989
|
if (command === "/ss")
|
|
@@ -3765,7 +4009,7 @@ function toPermissionMode(value) {
|
|
|
3765
4009
|
return null;
|
|
3766
4010
|
}
|
|
3767
4011
|
function toNonInteractivePermission(value) {
|
|
3768
|
-
if (value === "
|
|
4012
|
+
if (value === "deny" || value === "fail") {
|
|
3769
4013
|
return value;
|
|
3770
4014
|
}
|
|
3771
4015
|
return null;
|
|
@@ -3812,6 +4056,8 @@ var init_parse_command = __esm(() => {
|
|
|
3812
4056
|
"/cancel",
|
|
3813
4057
|
"/clear",
|
|
3814
4058
|
"/mode",
|
|
4059
|
+
"/replymode",
|
|
4060
|
+
"/config",
|
|
3815
4061
|
"/permission",
|
|
3816
4062
|
"/session",
|
|
3817
4063
|
"/workspace",
|
|
@@ -3820,6 +4066,17 @@ var init_parse_command = __esm(() => {
|
|
|
3820
4066
|
]);
|
|
3821
4067
|
});
|
|
3822
4068
|
|
|
4069
|
+
// src/commands/config-clone.ts
|
|
4070
|
+
function cloneAppConfig(config) {
|
|
4071
|
+
return {
|
|
4072
|
+
transport: { ...config.transport },
|
|
4073
|
+
logging: { ...config.logging },
|
|
4074
|
+
wechat: { ...config.wechat },
|
|
4075
|
+
agents: Object.fromEntries(Object.entries(config.agents).map(([name, agent]) => [name, { ...agent }])),
|
|
4076
|
+
workspaces: Object.fromEntries(Object.entries(config.workspaces).map(([name, workspace]) => [name, { ...workspace }]))
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
|
|
3823
4080
|
// src/commands/handlers/permission-handler.ts
|
|
3824
4081
|
function handlePermissionStatus(context, title) {
|
|
3825
4082
|
return { text: renderPermissionStatus(context.config, title) };
|
|
@@ -3828,9 +4085,17 @@ async function handlePermissionModeSet(context, mode) {
|
|
|
3828
4085
|
if (!context.config || !context.configStore) {
|
|
3829
4086
|
return { text: "当前没有加载可写入的配置。" };
|
|
3830
4087
|
}
|
|
4088
|
+
const previous = cloneAppConfig(context.config);
|
|
3831
4089
|
const updated = await context.configStore.updateTransport({
|
|
3832
4090
|
permissionMode: mode
|
|
3833
4091
|
});
|
|
4092
|
+
try {
|
|
4093
|
+
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
4094
|
+
} catch (error) {
|
|
4095
|
+
await context.configStore.save(previous);
|
|
4096
|
+
context.replaceConfig(previous);
|
|
4097
|
+
throw error;
|
|
4098
|
+
}
|
|
3834
4099
|
context.replaceConfig(updated);
|
|
3835
4100
|
return { text: renderPermissionStatus(context.config, "权限模式已更新:") };
|
|
3836
4101
|
}
|
|
@@ -3841,18 +4106,226 @@ async function handlePermissionAutoSet(context, policy) {
|
|
|
3841
4106
|
if (!context.config || !context.configStore) {
|
|
3842
4107
|
return { text: "当前没有加载可写入的配置。" };
|
|
3843
4108
|
}
|
|
4109
|
+
const previous = cloneAppConfig(context.config);
|
|
3844
4110
|
const updated = await context.configStore.updateTransport({
|
|
3845
4111
|
nonInteractivePermissions: policy
|
|
3846
4112
|
});
|
|
4113
|
+
try {
|
|
4114
|
+
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
4115
|
+
} catch (error) {
|
|
4116
|
+
await context.configStore.save(previous);
|
|
4117
|
+
context.replaceConfig(previous);
|
|
4118
|
+
throw error;
|
|
4119
|
+
}
|
|
3847
4120
|
context.replaceConfig(updated);
|
|
3848
4121
|
return { text: renderPermissionStatus(context.config, "非交互策略已更新:") };
|
|
3849
4122
|
}
|
|
3850
4123
|
function renderPermissionStatus(config, title) {
|
|
3851
4124
|
const permissionMode = config?.transport.permissionMode ?? "approve-all";
|
|
3852
|
-
const nonInteractivePermissions = config?.transport.nonInteractivePermissions ?? "
|
|
4125
|
+
const nonInteractivePermissions = config?.transport.nonInteractivePermissions ?? "deny";
|
|
3853
4126
|
return [title, `- mode: ${permissionMode}`, `- auto: ${nonInteractivePermissions}`].join(`
|
|
3854
4127
|
`);
|
|
3855
4128
|
}
|
|
4129
|
+
var permissionHelp;
|
|
4130
|
+
var init_permission_handler = __esm(() => {
|
|
4131
|
+
permissionHelp = {
|
|
4132
|
+
topic: "permission",
|
|
4133
|
+
aliases: ["pm"],
|
|
4134
|
+
summary: "查看和修改 transport 权限策略。",
|
|
4135
|
+
commands: [
|
|
4136
|
+
{ usage: "/pm 或 /permission", description: "查看当前权限模式" },
|
|
4137
|
+
{ usage: "/pm set <allow|read|deny>", description: "设置审批级别" },
|
|
4138
|
+
{ usage: "/pm auto", description: "查看当前非交互策略" },
|
|
4139
|
+
{ usage: "/pm auto <deny|fail>", description: "设置非交互策略" }
|
|
4140
|
+
],
|
|
4141
|
+
examples: ["/pm set read", "/pm auto deny"]
|
|
4142
|
+
};
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
// src/commands/handlers/config-handler.ts
|
|
4146
|
+
function handleConfigShow(context) {
|
|
4147
|
+
const lines = ["支持修改的配置字段:", ...SUPPORTED_CONFIG_PATHS.map((path11) => `- ${path11}`)];
|
|
4148
|
+
if (context.config) {
|
|
4149
|
+
lines.push("", "示例:", "- /config set wechat.replyMode final", "- /config set logging.level debug");
|
|
4150
|
+
}
|
|
4151
|
+
return { text: lines.join(`
|
|
4152
|
+
`) };
|
|
4153
|
+
}
|
|
4154
|
+
async function handleConfigSet(context, path11, rawValue) {
|
|
4155
|
+
if (!context.config || !context.configStore) {
|
|
4156
|
+
return { text: "当前没有加载可写入的配置。" };
|
|
4157
|
+
}
|
|
4158
|
+
const previous = cloneAppConfig(context.config);
|
|
4159
|
+
const updated = cloneAppConfig(context.config);
|
|
4160
|
+
const result = applySupportedConfigUpdate(updated, path11, rawValue);
|
|
4161
|
+
if ("error" in result) {
|
|
4162
|
+
return { text: result.error };
|
|
4163
|
+
}
|
|
4164
|
+
await context.configStore.save(updated);
|
|
4165
|
+
if (path11 === "transport.permissionMode" || path11 === "transport.nonInteractivePermissions") {
|
|
4166
|
+
try {
|
|
4167
|
+
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
4168
|
+
} catch (error) {
|
|
4169
|
+
await context.configStore.save(previous);
|
|
4170
|
+
context.replaceConfig(previous);
|
|
4171
|
+
throw error;
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
context.replaceConfig(updated);
|
|
4175
|
+
return { text: `配置已更新:${path11} = ${result.renderedValue}` };
|
|
4176
|
+
}
|
|
4177
|
+
function applySupportedConfigUpdate(config, path11, rawValue) {
|
|
4178
|
+
switch (path11) {
|
|
4179
|
+
case "transport.type": {
|
|
4180
|
+
const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
|
|
4181
|
+
if (!parsed)
|
|
4182
|
+
return { error: "transport.type 只支持:acpx-cli、acpx-bridge" };
|
|
4183
|
+
config.transport.type = parsed;
|
|
4184
|
+
return { renderedValue: parsed };
|
|
4185
|
+
}
|
|
4186
|
+
case "transport.command":
|
|
4187
|
+
if (!rawValue.trim())
|
|
4188
|
+
return { error: "transport.command 不能为空。" };
|
|
4189
|
+
config.transport.command = rawValue;
|
|
4190
|
+
return { renderedValue: rawValue };
|
|
4191
|
+
case "transport.sessionInitTimeoutMs": {
|
|
4192
|
+
const parsed = parsePositiveNumber(rawValue, "transport.sessionInitTimeoutMs");
|
|
4193
|
+
if ("error" in parsed)
|
|
4194
|
+
return parsed;
|
|
4195
|
+
config.transport.sessionInitTimeoutMs = parsed.value;
|
|
4196
|
+
return { renderedValue: String(parsed.value) };
|
|
4197
|
+
}
|
|
4198
|
+
case "transport.permissionMode": {
|
|
4199
|
+
const parsed = parseEnum(rawValue, ["approve-all", "approve-reads", "deny-all"]);
|
|
4200
|
+
if (!parsed)
|
|
4201
|
+
return { error: "transport.permissionMode 只支持:approve-all、approve-reads、deny-all" };
|
|
4202
|
+
config.transport.permissionMode = parsed;
|
|
4203
|
+
return { renderedValue: parsed };
|
|
4204
|
+
}
|
|
4205
|
+
case "transport.nonInteractivePermissions": {
|
|
4206
|
+
const parsed = parseEnum(rawValue, ["deny", "fail"]);
|
|
4207
|
+
if (!parsed)
|
|
4208
|
+
return { error: "transport.nonInteractivePermissions 只支持:deny、fail" };
|
|
4209
|
+
config.transport.nonInteractivePermissions = parsed;
|
|
4210
|
+
return { renderedValue: parsed };
|
|
4211
|
+
}
|
|
4212
|
+
case "logging.level": {
|
|
4213
|
+
const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
|
|
4214
|
+
if (!parsed)
|
|
4215
|
+
return { error: "logging.level 只支持:error、info、debug" };
|
|
4216
|
+
config.logging.level = parsed;
|
|
4217
|
+
return { renderedValue: parsed };
|
|
4218
|
+
}
|
|
4219
|
+
case "logging.maxSizeBytes": {
|
|
4220
|
+
const parsed = parsePositiveNumber(rawValue, "logging.maxSizeBytes");
|
|
4221
|
+
if ("error" in parsed)
|
|
4222
|
+
return parsed;
|
|
4223
|
+
config.logging.maxSizeBytes = parsed.value;
|
|
4224
|
+
return { renderedValue: String(parsed.value) };
|
|
4225
|
+
}
|
|
4226
|
+
case "logging.maxFiles": {
|
|
4227
|
+
const parsed = parsePositiveNumber(rawValue, "logging.maxFiles");
|
|
4228
|
+
if ("error" in parsed)
|
|
4229
|
+
return parsed;
|
|
4230
|
+
config.logging.maxFiles = parsed.value;
|
|
4231
|
+
return { renderedValue: String(parsed.value) };
|
|
4232
|
+
}
|
|
4233
|
+
case "logging.retentionDays": {
|
|
4234
|
+
const parsed = parsePositiveNumber(rawValue, "logging.retentionDays");
|
|
4235
|
+
if ("error" in parsed)
|
|
4236
|
+
return parsed;
|
|
4237
|
+
config.logging.retentionDays = parsed.value;
|
|
4238
|
+
return { renderedValue: String(parsed.value) };
|
|
4239
|
+
}
|
|
4240
|
+
case "wechat.replyMode": {
|
|
4241
|
+
const parsed = parseEnum(rawValue, ["stream", "final"]);
|
|
4242
|
+
if (!parsed)
|
|
4243
|
+
return { error: "wechat.replyMode 只支持:stream、final" };
|
|
4244
|
+
config.wechat.replyMode = parsed;
|
|
4245
|
+
return { renderedValue: parsed };
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
const agentMatch = path11.match(/^agents\.([^.]+)\.(driver|command)$/);
|
|
4249
|
+
if (agentMatch) {
|
|
4250
|
+
const [, name, field] = agentMatch;
|
|
4251
|
+
if (!name || !field) {
|
|
4252
|
+
return { error: `不支持修改这个配置路径:${path11}` };
|
|
4253
|
+
}
|
|
4254
|
+
const agent = config.agents[name];
|
|
4255
|
+
if (!agent) {
|
|
4256
|
+
return { error: `Agent「${name}」不存在,请先创建。` };
|
|
4257
|
+
}
|
|
4258
|
+
if (!rawValue.trim()) {
|
|
4259
|
+
return { error: `${path11} 不能为空。` };
|
|
4260
|
+
}
|
|
4261
|
+
if (field === "driver") {
|
|
4262
|
+
agent.driver = rawValue;
|
|
4263
|
+
} else {
|
|
4264
|
+
agent.command = rawValue;
|
|
4265
|
+
}
|
|
4266
|
+
return { renderedValue: rawValue };
|
|
4267
|
+
}
|
|
4268
|
+
const workspaceMatch = path11.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
|
|
4269
|
+
if (workspaceMatch) {
|
|
4270
|
+
const [, name, field] = workspaceMatch;
|
|
4271
|
+
if (!name || !field) {
|
|
4272
|
+
return { error: `不支持修改这个配置路径:${path11}` };
|
|
4273
|
+
}
|
|
4274
|
+
const workspace = config.workspaces[name];
|
|
4275
|
+
if (!workspace) {
|
|
4276
|
+
return { error: `工作区「${name}」不存在,请先创建。` };
|
|
4277
|
+
}
|
|
4278
|
+
if (!rawValue.trim()) {
|
|
4279
|
+
return { error: `${path11} 不能为空。` };
|
|
4280
|
+
}
|
|
4281
|
+
if (field === "cwd") {
|
|
4282
|
+
workspace.cwd = rawValue;
|
|
4283
|
+
} else {
|
|
4284
|
+
workspace.description = rawValue;
|
|
4285
|
+
}
|
|
4286
|
+
return { renderedValue: rawValue };
|
|
4287
|
+
}
|
|
4288
|
+
return { error: `不支持修改这个配置路径:${path11}` };
|
|
4289
|
+
}
|
|
4290
|
+
function parseEnum(value, allowed) {
|
|
4291
|
+
return allowed.includes(value) ? value : null;
|
|
4292
|
+
}
|
|
4293
|
+
function parsePositiveNumber(rawValue, path11) {
|
|
4294
|
+
const value = Number(rawValue);
|
|
4295
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
4296
|
+
return { error: `${path11} 必须是正数。` };
|
|
4297
|
+
}
|
|
4298
|
+
return { value };
|
|
4299
|
+
}
|
|
4300
|
+
var SUPPORTED_CONFIG_PATHS, configHelp;
|
|
4301
|
+
var init_config_handler = __esm(() => {
|
|
4302
|
+
SUPPORTED_CONFIG_PATHS = [
|
|
4303
|
+
"transport.type",
|
|
4304
|
+
"transport.command",
|
|
4305
|
+
"transport.sessionInitTimeoutMs",
|
|
4306
|
+
"transport.permissionMode",
|
|
4307
|
+
"transport.nonInteractivePermissions",
|
|
4308
|
+
"logging.level",
|
|
4309
|
+
"logging.maxSizeBytes",
|
|
4310
|
+
"logging.maxFiles",
|
|
4311
|
+
"logging.retentionDays",
|
|
4312
|
+
"wechat.replyMode",
|
|
4313
|
+
"agents.<name>.driver",
|
|
4314
|
+
"agents.<name>.command",
|
|
4315
|
+
"workspaces.<name>.cwd",
|
|
4316
|
+
"workspaces.<name>.description"
|
|
4317
|
+
];
|
|
4318
|
+
configHelp = {
|
|
4319
|
+
topic: "config",
|
|
4320
|
+
aliases: [],
|
|
4321
|
+
summary: "查看和修改受支持的配置字段。",
|
|
4322
|
+
commands: [
|
|
4323
|
+
{ usage: "/config", description: "查看当前支持修改的配置路径" },
|
|
4324
|
+
{ usage: "/config set <path> <value>", description: "修改一个受支持的配置值" }
|
|
4325
|
+
],
|
|
4326
|
+
examples: ["/config set wechat.replyMode final", "/config set logging.level debug"]
|
|
4327
|
+
};
|
|
4328
|
+
});
|
|
3856
4329
|
|
|
3857
4330
|
// src/commands/handlers/session-handler.ts
|
|
3858
4331
|
async function handleSessions(context, chatKey) {
|
|
@@ -3889,8 +4362,8 @@ async function handleSessionNew(context, chatKey, alias, agent, workspace) {
|
|
|
3889
4362
|
});
|
|
3890
4363
|
return { text: `会话「${alias}」已创建并切换` };
|
|
3891
4364
|
}
|
|
3892
|
-
async function handleSessionShortcut(context, chatKey, agent,
|
|
3893
|
-
return await context.lifecycle.handleSessionShortcut(chatKey, agent,
|
|
4365
|
+
async function handleSessionShortcut(context, chatKey, agent, target, createNew) {
|
|
4366
|
+
return await context.lifecycle.handleSessionShortcut(chatKey, agent, target, createNew);
|
|
3894
4367
|
}
|
|
3895
4368
|
async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
|
|
3896
4369
|
const attached = context.lifecycle.resolveSession(alias, agent, workspace, transportSession);
|
|
@@ -3946,6 +4419,42 @@ async function handleModeSet(context, chatKey, modeId) {
|
|
|
3946
4419
|
await context.sessions.setCurrentSessionMode(chatKey, modeId);
|
|
3947
4420
|
return { text: `已设置当前会话 mode:${modeId}` };
|
|
3948
4421
|
}
|
|
4422
|
+
async function handleReplyModeShow(context, chatKey) {
|
|
4423
|
+
const session = await context.sessions.getCurrentSession(chatKey);
|
|
4424
|
+
if (!session) {
|
|
4425
|
+
return { text: NO_CURRENT_SESSION_TEXT };
|
|
4426
|
+
}
|
|
4427
|
+
const globalDefault = context.config?.wechat.replyMode ?? "stream";
|
|
4428
|
+
const sessionOverride = session.replyMode;
|
|
4429
|
+
const effective = sessionOverride ?? globalDefault;
|
|
4430
|
+
return {
|
|
4431
|
+
text: [
|
|
4432
|
+
"当前 reply mode:",
|
|
4433
|
+
`- 会话:${session.alias}`,
|
|
4434
|
+
`- 全局默认:${globalDefault}`,
|
|
4435
|
+
`- 当前会话覆盖:${sessionOverride ?? "未设置"}`,
|
|
4436
|
+
`- 当前生效:${effective}`
|
|
4437
|
+
].join(`
|
|
4438
|
+
`)
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
async function handleReplyModeSet(context, chatKey, replyMode) {
|
|
4442
|
+
const session = await context.sessions.getCurrentSession(chatKey);
|
|
4443
|
+
if (!session) {
|
|
4444
|
+
return { text: NO_CURRENT_SESSION_TEXT };
|
|
4445
|
+
}
|
|
4446
|
+
await context.sessions.setCurrentSessionReplyMode(chatKey, replyMode);
|
|
4447
|
+
return { text: `已设置当前会话 reply mode:${replyMode}` };
|
|
4448
|
+
}
|
|
4449
|
+
async function handleReplyModeReset(context, chatKey) {
|
|
4450
|
+
const session = await context.sessions.getCurrentSession(chatKey);
|
|
4451
|
+
if (!session) {
|
|
4452
|
+
return { text: NO_CURRENT_SESSION_TEXT };
|
|
4453
|
+
}
|
|
4454
|
+
await context.sessions.setCurrentSessionReplyMode(chatKey, undefined);
|
|
4455
|
+
const globalDefault = context.config?.wechat.replyMode ?? "stream";
|
|
4456
|
+
return { text: `已重置当前会话 reply mode,当前回退到全局默认:${globalDefault}` };
|
|
4457
|
+
}
|
|
3949
4458
|
async function handleStatus(context, chatKey) {
|
|
3950
4459
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
3951
4460
|
if (!session) {
|
|
@@ -3982,18 +4491,79 @@ async function handlePrompt(context, chatKey, text, reply) {
|
|
|
3982
4491
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
3983
4492
|
}
|
|
3984
4493
|
try {
|
|
3985
|
-
const
|
|
4494
|
+
const effectiveReplyMode = session.replyMode ?? context.config?.wechat.replyMode ?? "stream";
|
|
4495
|
+
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4496
|
+
const result = await context.interaction.promptTransportSession(session, text, transportReply);
|
|
3986
4497
|
return { text: result.text };
|
|
3987
4498
|
} catch (error) {
|
|
3988
4499
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error);
|
|
3989
4500
|
if (recovered) {
|
|
3990
|
-
const
|
|
4501
|
+
const effectiveReplyMode = recovered.replyMode ?? context.config?.wechat.replyMode ?? "stream";
|
|
4502
|
+
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4503
|
+
const result = await context.interaction.promptTransportSession(recovered, text, transportReply);
|
|
3991
4504
|
return { text: result.text };
|
|
3992
4505
|
}
|
|
3993
4506
|
return context.recovery.renderTransportError(session, error);
|
|
3994
4507
|
}
|
|
3995
4508
|
}
|
|
3996
|
-
var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。";
|
|
4509
|
+
var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
|
|
4510
|
+
var init_session_handler = __esm(() => {
|
|
4511
|
+
sessionHelp = {
|
|
4512
|
+
topic: "session",
|
|
4513
|
+
aliases: ["ss", "sessions"],
|
|
4514
|
+
summary: "创建、恢复、切换和重置逻辑会话。",
|
|
4515
|
+
commands: [
|
|
4516
|
+
{ usage: "/sessions", description: "查看当前会话列表" },
|
|
4517
|
+
{ usage: "/session 或 /ss", description: "查看会话列表" },
|
|
4518
|
+
{ usage: "/ss <agent> (-d <path> | --ws <name>)", description: "快速新建或复用一个会话" },
|
|
4519
|
+
{ usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
|
|
4520
|
+
{ usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
|
|
4521
|
+
{ usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
|
|
4522
|
+
{ usage: "/use <alias>", description: "切换当前会话" },
|
|
4523
|
+
{ usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
|
|
4524
|
+
],
|
|
4525
|
+
examples: ["/ss codex -d /absolute/path/to/repo", "/use backend-fix", "/session reset"]
|
|
4526
|
+
};
|
|
4527
|
+
modeHelp = {
|
|
4528
|
+
topic: "mode",
|
|
4529
|
+
aliases: [],
|
|
4530
|
+
summary: "查看或设置当前会话 mode。",
|
|
4531
|
+
commands: [
|
|
4532
|
+
{ usage: "/mode", description: "查看当前会话已保存的 mode" },
|
|
4533
|
+
{ usage: "/mode <id>", description: "设置当前会话 mode" }
|
|
4534
|
+
],
|
|
4535
|
+
examples: ["/mode", "/mode plan"]
|
|
4536
|
+
};
|
|
4537
|
+
replyModeHelp = {
|
|
4538
|
+
topic: "replymode",
|
|
4539
|
+
aliases: [],
|
|
4540
|
+
summary: "查看或设置当前逻辑会话的回复输出模式。",
|
|
4541
|
+
commands: [
|
|
4542
|
+
{ usage: "/replymode", description: "查看全局默认、当前覆盖和实际生效值" },
|
|
4543
|
+
{ usage: "/replymode stream", description: "当前会话使用流式回复" },
|
|
4544
|
+
{ usage: "/replymode final", description: "当前会话只发送最终文本" },
|
|
4545
|
+
{ usage: "/replymode reset", description: "清除当前会话覆盖并回退到全局默认" }
|
|
4546
|
+
],
|
|
4547
|
+
examples: ["/replymode", "/replymode final"]
|
|
4548
|
+
};
|
|
4549
|
+
statusHelp = {
|
|
4550
|
+
topic: "status",
|
|
4551
|
+
aliases: [],
|
|
4552
|
+
summary: "查看当前选中会话的状态。",
|
|
4553
|
+
commands: [{ usage: "/status", description: "查看当前会话状态" }],
|
|
4554
|
+
examples: ["/status"]
|
|
4555
|
+
};
|
|
4556
|
+
cancelHelp = {
|
|
4557
|
+
topic: "cancel",
|
|
4558
|
+
aliases: ["stop"],
|
|
4559
|
+
summary: "取消当前会话里正在执行的任务。",
|
|
4560
|
+
commands: [
|
|
4561
|
+
{ usage: "/cancel", description: "取消当前任务" },
|
|
4562
|
+
{ usage: "/stop", description: "取消当前任务(/cancel 别名)" }
|
|
4563
|
+
],
|
|
4564
|
+
examples: ["/cancel"]
|
|
4565
|
+
};
|
|
4566
|
+
});
|
|
3997
4567
|
|
|
3998
4568
|
// src/commands/transport-diagnostics.ts
|
|
3999
4569
|
function summarizeTransportError(message) {
|
|
@@ -4057,47 +4627,6 @@ function isPartialPromptOutputError(message) {
|
|
|
4057
4627
|
}
|
|
4058
4628
|
|
|
4059
4629
|
// src/formatting/render-text.ts
|
|
4060
|
-
function renderHelpText() {
|
|
4061
|
-
return [
|
|
4062
|
-
"可用命令:",
|
|
4063
|
-
"",
|
|
4064
|
-
"先看这 3 个:",
|
|
4065
|
-
"/ss new <agent> -d <path> - 新建会话",
|
|
4066
|
-
"/use <alias> - 切会话",
|
|
4067
|
-
"/status - 看状态",
|
|
4068
|
-
"",
|
|
4069
|
-
"Agent:",
|
|
4070
|
-
"/agents - 看 Agent",
|
|
4071
|
-
"/agent add <codex|claude> - 加 Agent",
|
|
4072
|
-
"/agent rm <name> - 删 Agent",
|
|
4073
|
-
"",
|
|
4074
|
-
"工作区:",
|
|
4075
|
-
"/workspaces - 看工作区",
|
|
4076
|
-
"/workspace 或 /ws - 工作区命令",
|
|
4077
|
-
"/ws new <name> -d <path> - 加工作区",
|
|
4078
|
-
"/workspace rm <name> - 删工作区",
|
|
4079
|
-
"",
|
|
4080
|
-
"会话:",
|
|
4081
|
-
"/sessions - 看会话",
|
|
4082
|
-
"/session 或 /ss - 会话命令",
|
|
4083
|
-
"/ss <agent> -d <path> - 快速新建",
|
|
4084
|
-
"/ss new <agent> -d <path> - 新建会话",
|
|
4085
|
-
"/ss new <alias> -a <name> --ws <name> - 指定配置新建",
|
|
4086
|
-
"/ss attach <alias> -a <name> --ws <name> --name <transport-session> - 挂已有会话",
|
|
4087
|
-
"/use <alias> - 切会话",
|
|
4088
|
-
"/session reset 或 /clear - 清上下文",
|
|
4089
|
-
"",
|
|
4090
|
-
"权限:",
|
|
4091
|
-
"/pm 或 /permission - 权限设置",
|
|
4092
|
-
"/pm set <allow|read|deny> - 设审批级别",
|
|
4093
|
-
"/pm auto [allow|deny|fail] - 设自动处理",
|
|
4094
|
-
"",
|
|
4095
|
-
"常用:",
|
|
4096
|
-
"/status - 看状态",
|
|
4097
|
-
"/cancel 或 /stop - 停当前任务"
|
|
4098
|
-
].join(`
|
|
4099
|
-
`);
|
|
4100
|
-
}
|
|
4101
4630
|
function renderAgents(config) {
|
|
4102
4631
|
const names = Object.keys(config.agents);
|
|
4103
4632
|
if (names.length === 0) {
|
|
@@ -4115,12 +4644,6 @@ function renderWorkspaces(config) {
|
|
|
4115
4644
|
`);
|
|
4116
4645
|
}
|
|
4117
4646
|
|
|
4118
|
-
// src/commands/handlers/help-handler.ts
|
|
4119
|
-
function handleHelp() {
|
|
4120
|
-
return { text: renderHelpText() };
|
|
4121
|
-
}
|
|
4122
|
-
var init_help_handler = () => {};
|
|
4123
|
-
|
|
4124
4647
|
// src/config/agent-templates.ts
|
|
4125
4648
|
function getAgentTemplate(name) {
|
|
4126
4649
|
const template = TEMPLATES[name];
|
|
@@ -4173,13 +4696,25 @@ async function handleAgentRemove(context, agentName) {
|
|
|
4173
4696
|
context.replaceConfig(updated);
|
|
4174
4697
|
return { text: `Agent「${agentName}」已删除` };
|
|
4175
4698
|
}
|
|
4699
|
+
var agentHelp;
|
|
4176
4700
|
var init_agent_handler = __esm(() => {
|
|
4177
4701
|
init_agent_templates();
|
|
4702
|
+
agentHelp = {
|
|
4703
|
+
topic: "agent",
|
|
4704
|
+
aliases: ["agents"],
|
|
4705
|
+
summary: "管理已注册的 Agent。",
|
|
4706
|
+
commands: [
|
|
4707
|
+
{ usage: "/agents", description: "查看当前已注册的 Agent" },
|
|
4708
|
+
{ usage: "/agent add <codex|claude>", description: "添加内置 Agent 模板" },
|
|
4709
|
+
{ usage: "/agent rm <name>", description: "删除一个 Agent" }
|
|
4710
|
+
],
|
|
4711
|
+
examples: ["/agent add claude", "/agent rm codex"]
|
|
4712
|
+
};
|
|
4178
4713
|
});
|
|
4179
4714
|
|
|
4180
4715
|
// src/commands/handlers/workspace-handler.ts
|
|
4181
4716
|
import { access } from "node:fs/promises";
|
|
4182
|
-
import { homedir as
|
|
4717
|
+
import { homedir as homedir3 } from "node:os";
|
|
4183
4718
|
import { normalize } from "node:path";
|
|
4184
4719
|
function handleWorkspaces(context) {
|
|
4185
4720
|
return { text: context.config ? renderWorkspaces(context.config) : "No config loaded." };
|
|
@@ -4213,24 +4748,127 @@ async function pathExists(path11) {
|
|
|
4213
4748
|
}
|
|
4214
4749
|
}
|
|
4215
4750
|
function normalizePathForWorkspace(path11) {
|
|
4216
|
-
const expanded = path11.startsWith("~") ?
|
|
4751
|
+
const expanded = path11.startsWith("~") ? homedir3() + path11.slice(1) : path11;
|
|
4217
4752
|
return normalize(expanded);
|
|
4218
4753
|
}
|
|
4219
|
-
var
|
|
4754
|
+
var workspaceHelp;
|
|
4755
|
+
var init_workspace_handler = __esm(() => {
|
|
4756
|
+
workspaceHelp = {
|
|
4757
|
+
topic: "workspace",
|
|
4758
|
+
aliases: ["ws", "workspaces"],
|
|
4759
|
+
summary: "管理已注册的工作区。",
|
|
4760
|
+
commands: [
|
|
4761
|
+
{ usage: "/workspaces", description: "查看当前已注册的工作区" },
|
|
4762
|
+
{ usage: "/workspace 或 /ws", description: "查看工作区列表" },
|
|
4763
|
+
{ usage: "/ws new <name> -d <path>", description: "添加工作区" },
|
|
4764
|
+
{ usage: "/workspace rm <name>", description: "删除工作区" }
|
|
4765
|
+
],
|
|
4766
|
+
examples: ['/ws new backend -d "/tmp/backend"', "/workspace rm backend"]
|
|
4767
|
+
};
|
|
4768
|
+
});
|
|
4769
|
+
|
|
4770
|
+
// src/commands/help/help-registry.ts
|
|
4771
|
+
function getHelpTopic(topic) {
|
|
4772
|
+
return HELP_TOPIC_MAP.get(topic) ?? null;
|
|
4773
|
+
}
|
|
4774
|
+
function listHelpTopics() {
|
|
4775
|
+
return HELP_TOPICS;
|
|
4776
|
+
}
|
|
4777
|
+
var HELP_TOPICS, HELP_TOPIC_MAP;
|
|
4778
|
+
var init_help_registry = __esm(() => {
|
|
4779
|
+
init_agent_handler();
|
|
4780
|
+
init_config_handler();
|
|
4781
|
+
init_permission_handler();
|
|
4782
|
+
init_session_handler();
|
|
4783
|
+
init_workspace_handler();
|
|
4784
|
+
HELP_TOPICS = [
|
|
4785
|
+
sessionHelp,
|
|
4786
|
+
workspaceHelp,
|
|
4787
|
+
agentHelp,
|
|
4788
|
+
permissionHelp,
|
|
4789
|
+
configHelp,
|
|
4790
|
+
modeHelp,
|
|
4791
|
+
replyModeHelp,
|
|
4792
|
+
statusHelp,
|
|
4793
|
+
cancelHelp
|
|
4794
|
+
];
|
|
4795
|
+
HELP_TOPIC_MAP = new Map;
|
|
4796
|
+
for (const topic of HELP_TOPICS) {
|
|
4797
|
+
HELP_TOPIC_MAP.set(topic.topic, topic);
|
|
4798
|
+
for (const alias of topic.aliases) {
|
|
4799
|
+
HELP_TOPIC_MAP.set(alias, topic);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
});
|
|
4803
|
+
|
|
4804
|
+
// src/commands/handlers/help-handler.ts
|
|
4805
|
+
function handleHelp(topic) {
|
|
4806
|
+
if (!topic) {
|
|
4807
|
+
return { text: renderHelpIndex() };
|
|
4808
|
+
}
|
|
4809
|
+
const entry = getHelpTopic(topic);
|
|
4810
|
+
if (!entry) {
|
|
4811
|
+
return { text: renderUnknownHelpTopic(topic) };
|
|
4812
|
+
}
|
|
4813
|
+
return { text: renderHelpTopic(entry) };
|
|
4814
|
+
}
|
|
4815
|
+
function renderHelpIndex() {
|
|
4816
|
+
const topics = listHelpTopics();
|
|
4817
|
+
return [
|
|
4818
|
+
"常用入口:",
|
|
4819
|
+
"- /ss <agent> (-d <path> | --ws <name>) - 快速新建或切到会话",
|
|
4820
|
+
"- /use <alias> - 切换当前会话",
|
|
4821
|
+
"- /status - 查看当前会话状态",
|
|
4822
|
+
"",
|
|
4823
|
+
"顶级命令:",
|
|
4824
|
+
...topics.map((topic) => `- ${topic.topic} - ${topic.summary}`),
|
|
4825
|
+
"",
|
|
4826
|
+
"查看专题说明:",
|
|
4827
|
+
"- /help <topic>",
|
|
4828
|
+
"- 例如:/help ss、/help ws、/help pm"
|
|
4829
|
+
].join(`
|
|
4830
|
+
`);
|
|
4831
|
+
}
|
|
4832
|
+
function renderHelpTopic(topic) {
|
|
4833
|
+
return [
|
|
4834
|
+
`帮助主题:${topic.topic}`,
|
|
4835
|
+
`说明:${topic.summary}`,
|
|
4836
|
+
...topic.aliases.length > 0 ? [`别名:${topic.aliases.join("、")}`] : [],
|
|
4837
|
+
"",
|
|
4838
|
+
"命令:",
|
|
4839
|
+
...topic.commands.map((command) => `- ${command.usage} - ${command.description}`),
|
|
4840
|
+
...topic.examples && topic.examples.length > 0 ? ["", "示例:", ...topic.examples.map((example) => `- ${example}`)] : []
|
|
4841
|
+
].join(`
|
|
4842
|
+
`);
|
|
4843
|
+
}
|
|
4844
|
+
function renderUnknownHelpTopic(topic) {
|
|
4845
|
+
return [
|
|
4846
|
+
`未知帮助主题:${topic}`,
|
|
4847
|
+
"",
|
|
4848
|
+
"可用主题:",
|
|
4849
|
+
...listHelpTopics().map((entry) => `- ${entry.topic}`)
|
|
4850
|
+
].join(`
|
|
4851
|
+
`);
|
|
4852
|
+
}
|
|
4853
|
+
var init_help_handler = __esm(() => {
|
|
4854
|
+
init_help_registry();
|
|
4855
|
+
});
|
|
4220
4856
|
|
|
4221
4857
|
// src/commands/handlers/session-shortcut-handler.ts
|
|
4222
4858
|
import { access as access2 } from "node:fs/promises";
|
|
4223
4859
|
import { basename as basename2, normalize as normalize2 } from "node:path";
|
|
4224
|
-
import { homedir as
|
|
4225
|
-
async function handleSessionShortcutCommand(context, ops, chatKey, agent,
|
|
4860
|
+
import { homedir as homedir4 } from "node:os";
|
|
4861
|
+
async function handleSessionShortcutCommand(context, ops, chatKey, agent, target, createNew) {
|
|
4226
4862
|
if (!context.config || !context.configStore) {
|
|
4227
4863
|
return { text: "当前没有加载可写入的配置。" };
|
|
4228
4864
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4865
|
+
if (!context.config.agents[agent]) {
|
|
4866
|
+
return { text: `agent "${agent}" is not registered` };
|
|
4867
|
+
}
|
|
4868
|
+
const workspace = await resolveShortcutWorkspace(context, target);
|
|
4869
|
+
if ("error" in workspace) {
|
|
4870
|
+
return { text: workspace.error };
|
|
4232
4871
|
}
|
|
4233
|
-
const workspace = await resolveShortcutWorkspace(context, cwd);
|
|
4234
4872
|
await context.logger.info("session.shortcut.workspace", "resolved shortcut workspace", {
|
|
4235
4873
|
workspace: workspace.name,
|
|
4236
4874
|
cwd: workspace.cwd,
|
|
@@ -4282,7 +4920,23 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, cwdInp
|
|
|
4282
4920
|
`)
|
|
4283
4921
|
};
|
|
4284
4922
|
}
|
|
4285
|
-
async function resolveShortcutWorkspace(context,
|
|
4923
|
+
async function resolveShortcutWorkspace(context, target) {
|
|
4924
|
+
if (target.workspace) {
|
|
4925
|
+
const workspace = context.config?.workspaces[target.workspace];
|
|
4926
|
+
if (!workspace) {
|
|
4927
|
+
return { error: `workspace "${target.workspace}" is not registered` };
|
|
4928
|
+
}
|
|
4929
|
+
return {
|
|
4930
|
+
name: target.workspace,
|
|
4931
|
+
cwd: workspace.cwd,
|
|
4932
|
+
reused: true
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
const cwdInput = target.cwd ?? "";
|
|
4936
|
+
const cwd = normalizePathForWorkspace2(cwdInput);
|
|
4937
|
+
if (!await pathExists2(cwd)) {
|
|
4938
|
+
return { error: `工作区路径不存在:${cwdInput}` };
|
|
4939
|
+
}
|
|
4286
4940
|
const existingByPath = Object.entries(context.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
|
|
4287
4941
|
if (existingByPath) {
|
|
4288
4942
|
return {
|
|
@@ -4343,7 +4997,7 @@ async function pathExists2(path11) {
|
|
|
4343
4997
|
}
|
|
4344
4998
|
}
|
|
4345
4999
|
function normalizePathForWorkspace2(path11) {
|
|
4346
|
-
const expanded = path11.startsWith("~") ?
|
|
5000
|
+
const expanded = path11.startsWith("~") ? homedir4() + path11.slice(1) : path11;
|
|
4347
5001
|
return normalize2(expanded);
|
|
4348
5002
|
}
|
|
4349
5003
|
function sameWorkspacePath(left, right) {
|
|
@@ -4384,15 +5038,19 @@ function renderTransportError(session, error) {
|
|
|
4384
5038
|
function renderSessionCreationError(session, error) {
|
|
4385
5039
|
const message = error instanceof Error ? error.message : String(error);
|
|
4386
5040
|
if (message.includes("timed out") && message.includes("sessions new")) {
|
|
4387
|
-
return
|
|
5041
|
+
return renderSessionCreationFailure(session, message);
|
|
4388
5042
|
}
|
|
4389
5043
|
throw error;
|
|
4390
5044
|
}
|
|
4391
5045
|
function renderSessionCreationVerificationError(session) {
|
|
5046
|
+
return renderSessionCreationFailure(session, "未检测到可用的后端会话。");
|
|
5047
|
+
}
|
|
5048
|
+
function renderSessionCreationFailure(session, detail) {
|
|
4392
5049
|
return {
|
|
4393
5050
|
text: [
|
|
4394
|
-
"
|
|
4395
|
-
|
|
5051
|
+
"会话创建失败。",
|
|
5052
|
+
`错误信息:${summarizeTransportError(detail)}`,
|
|
5053
|
+
`如果你要先绑定一个已有会话,可以执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
|
|
4396
5054
|
].join(`
|
|
4397
5055
|
`)
|
|
4398
5056
|
};
|
|
@@ -4492,7 +5150,7 @@ class CommandRouter {
|
|
|
4492
5150
|
`)
|
|
4493
5151
|
};
|
|
4494
5152
|
case "help":
|
|
4495
|
-
return handleHelp();
|
|
5153
|
+
return handleHelp(command.topic);
|
|
4496
5154
|
case "agents":
|
|
4497
5155
|
return handleAgents(this.createHandlerContext());
|
|
4498
5156
|
case "agent.add":
|
|
@@ -4507,6 +5165,10 @@ class CommandRouter {
|
|
|
4507
5165
|
return handlePermissionAutoStatus(this.createHandlerContext(), "当前非交互策略:");
|
|
4508
5166
|
case "permission.auto.set":
|
|
4509
5167
|
return await handlePermissionAutoSet(this.createHandlerContext(), command.policy);
|
|
5168
|
+
case "config.show":
|
|
5169
|
+
return handleConfigShow(this.createHandlerContext());
|
|
5170
|
+
case "config.set":
|
|
5171
|
+
return await handleConfigSet(this.createHandlerContext(), command.path, command.value);
|
|
4510
5172
|
case "workspaces":
|
|
4511
5173
|
return handleWorkspaces(this.createHandlerContext());
|
|
4512
5174
|
case "workspace.new":
|
|
@@ -4518,9 +5180,9 @@ class CommandRouter {
|
|
|
4518
5180
|
case "session.new":
|
|
4519
5181
|
return await handleSessionNew(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace);
|
|
4520
5182
|
case "session.shortcut":
|
|
4521
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command
|
|
5183
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command, false);
|
|
4522
5184
|
case "session.shortcut.new":
|
|
4523
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command
|
|
5185
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command, true);
|
|
4524
5186
|
case "session.attach":
|
|
4525
5187
|
return await handleSessionAttach(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
|
|
4526
5188
|
case "session.use":
|
|
@@ -4529,6 +5191,12 @@ class CommandRouter {
|
|
|
4529
5191
|
return await handleModeShow(this.createSessionHandlerContext(), chatKey);
|
|
4530
5192
|
case "mode.set":
|
|
4531
5193
|
return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
|
|
5194
|
+
case "replymode.show":
|
|
5195
|
+
return await handleReplyModeShow(this.createSessionHandlerContext(), chatKey);
|
|
5196
|
+
case "replymode.set":
|
|
5197
|
+
return await handleReplyModeSet(this.createSessionHandlerContext(), chatKey, command.replyMode);
|
|
5198
|
+
case "replymode.reset":
|
|
5199
|
+
return await handleReplyModeReset(this.createSessionHandlerContext(), chatKey);
|
|
4532
5200
|
case "status":
|
|
4533
5201
|
return await handleStatus(this.createSessionHandlerContext(), chatKey);
|
|
4534
5202
|
case "cancel":
|
|
@@ -4566,7 +5234,7 @@ class CommandRouter {
|
|
|
4566
5234
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
4567
5235
|
ensureTransportSession: (session) => this.ensureTransportSession(session),
|
|
4568
5236
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
4569
|
-
handleSessionShortcut: (chatKey, agent,
|
|
5237
|
+
handleSessionShortcut: (chatKey, agent, target, createNew) => handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(), chatKey, agent, target, createNew),
|
|
4570
5238
|
resetCurrentSession: (chatKey) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(), chatKey),
|
|
4571
5239
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
4572
5240
|
};
|
|
@@ -4615,6 +5283,8 @@ class CommandRouter {
|
|
|
4615
5283
|
return;
|
|
4616
5284
|
}
|
|
4617
5285
|
this.config.transport = { ...updated.transport };
|
|
5286
|
+
this.config.logging = { ...updated.logging };
|
|
5287
|
+
this.config.wechat = { ...updated.wechat };
|
|
4618
5288
|
this.config.agents = { ...updated.agents };
|
|
4619
5289
|
this.config.workspaces = { ...updated.workspaces };
|
|
4620
5290
|
}
|
|
@@ -4705,6 +5375,9 @@ var init_command_router = __esm(() => {
|
|
|
4705
5375
|
init_acpx_session_index();
|
|
4706
5376
|
init_prompt_output();
|
|
4707
5377
|
init_parse_command();
|
|
5378
|
+
init_permission_handler();
|
|
5379
|
+
init_config_handler();
|
|
5380
|
+
init_session_handler();
|
|
4708
5381
|
init_help_handler();
|
|
4709
5382
|
init_agent_handler();
|
|
4710
5383
|
init_workspace_handler();
|
|
@@ -4729,12 +5402,12 @@ function isLegacyCodexCommand(command) {
|
|
|
4729
5402
|
}
|
|
4730
5403
|
|
|
4731
5404
|
// src/config/load-config.ts
|
|
4732
|
-
import { readFile as
|
|
5405
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
4733
5406
|
function isRecord(value) {
|
|
4734
5407
|
return typeof value === "object" && value !== null;
|
|
4735
5408
|
}
|
|
4736
5409
|
async function loadConfig(path11, options = {}) {
|
|
4737
|
-
const raw = JSON.parse(await
|
|
5410
|
+
const raw = JSON.parse(await readFile5(path11, "utf8"));
|
|
4738
5411
|
return parseConfig(raw, options);
|
|
4739
5412
|
}
|
|
4740
5413
|
function parseConfig(raw, options = {}) {
|
|
@@ -4754,8 +5427,8 @@ function parseConfig(raw, options = {}) {
|
|
|
4754
5427
|
if ("permissionMode" in transport && transport.permissionMode !== "approve-all" && transport.permissionMode !== "approve-reads" && transport.permissionMode !== "deny-all") {
|
|
4755
5428
|
throw new Error("transport.permissionMode must be approve-all, approve-reads, or deny-all");
|
|
4756
5429
|
}
|
|
4757
|
-
if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "
|
|
4758
|
-
throw new Error("transport.nonInteractivePermissions must be
|
|
5430
|
+
if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
|
|
5431
|
+
throw new Error("transport.nonInteractivePermissions must be deny or fail");
|
|
4759
5432
|
}
|
|
4760
5433
|
if (!isRecord(raw.agents)) {
|
|
4761
5434
|
throw new Error("agents must be an object");
|
|
@@ -4764,9 +5437,13 @@ function parseConfig(raw, options = {}) {
|
|
|
4764
5437
|
throw new Error("workspaces must be an object");
|
|
4765
5438
|
}
|
|
4766
5439
|
const logging = raw.logging;
|
|
5440
|
+
const wechat = raw.wechat;
|
|
4767
5441
|
if (logging !== undefined && !isRecord(logging)) {
|
|
4768
5442
|
throw new Error("logging must be an object");
|
|
4769
5443
|
}
|
|
5444
|
+
if (wechat !== undefined && !isRecord(wechat)) {
|
|
5445
|
+
throw new Error("wechat must be an object");
|
|
5446
|
+
}
|
|
4770
5447
|
if (isRecord(logging) && "level" in logging && logging.level !== "error" && logging.level !== "info" && logging.level !== "debug") {
|
|
4771
5448
|
throw new Error("logging.level must be error, info, or debug");
|
|
4772
5449
|
}
|
|
@@ -4775,6 +5452,9 @@ function parseConfig(raw, options = {}) {
|
|
|
4775
5452
|
throw new Error(`logging.${field} must be a positive number`);
|
|
4776
5453
|
}
|
|
4777
5454
|
}
|
|
5455
|
+
if (isRecord(wechat) && "replyMode" in wechat && wechat.replyMode !== "stream" && wechat.replyMode !== "final") {
|
|
5456
|
+
throw new Error("wechat.replyMode must be stream or final");
|
|
5457
|
+
}
|
|
4778
5458
|
for (const [name, agent] of Object.entries(raw.agents)) {
|
|
4779
5459
|
if (!isRecord(agent) || typeof agent.driver !== "string" || agent.driver.length === 0) {
|
|
4780
5460
|
throw new Error(`agent "${name}" must define a non-empty driver`);
|
|
@@ -4811,9 +5491,10 @@ function parseConfig(raw, options = {}) {
|
|
|
4811
5491
|
}
|
|
4812
5492
|
const transportType = transport.type === "acpx-cli" || transport.type === "acpx-bridge" ? transport.type : "acpx-bridge";
|
|
4813
5493
|
const permissionMode = transport.permissionMode === "approve-all" || transport.permissionMode === "approve-reads" || transport.permissionMode === "deny-all" ? transport.permissionMode : DEFAULT_PERMISSION_MODE;
|
|
4814
|
-
const nonInteractivePermissions = transport.nonInteractivePermissions === "
|
|
5494
|
+
const nonInteractivePermissions = transport.nonInteractivePermissions === "deny" || transport.nonInteractivePermissions === "fail" ? transport.nonInteractivePermissions : DEFAULT_NON_INTERACTIVE_PERMISSIONS;
|
|
4815
5495
|
const loggingLevel = logging?.level;
|
|
4816
5496
|
const resolvedLoggingLevel = loggingLevel === "error" || loggingLevel === "info" || loggingLevel === "debug" ? loggingLevel : options.defaultLoggingLevel ?? DEFAULT_LOGGING_CONFIG.level;
|
|
5497
|
+
const replyMode = wechat?.replyMode === "stream" || wechat?.replyMode === "final" ? wechat.replyMode : DEFAULT_WECHAT_REPLY_MODE;
|
|
4817
5498
|
return {
|
|
4818
5499
|
transport: {
|
|
4819
5500
|
...typeof transport.command === "string" ? { command: transport.command } : {},
|
|
@@ -4828,11 +5509,14 @@ function parseConfig(raw, options = {}) {
|
|
|
4828
5509
|
maxFiles: typeof logging?.maxFiles === "number" ? logging.maxFiles : DEFAULT_LOGGING_CONFIG.maxFiles,
|
|
4829
5510
|
retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays
|
|
4830
5511
|
},
|
|
5512
|
+
wechat: {
|
|
5513
|
+
replyMode
|
|
5514
|
+
},
|
|
4831
5515
|
agents,
|
|
4832
5516
|
workspaces
|
|
4833
5517
|
};
|
|
4834
5518
|
}
|
|
4835
|
-
var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "
|
|
5519
|
+
var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_WECHAT_REPLY_MODE = "stream";
|
|
4836
5520
|
var init_load_config = __esm(() => {
|
|
4837
5521
|
DEFAULT_LOGGING_CONFIG = {
|
|
4838
5522
|
level: "info",
|
|
@@ -4843,8 +5527,8 @@ var init_load_config = __esm(() => {
|
|
|
4843
5527
|
});
|
|
4844
5528
|
|
|
4845
5529
|
// src/config/config-store.ts
|
|
4846
|
-
import { mkdir as
|
|
4847
|
-
import { dirname as
|
|
5530
|
+
import { mkdir as mkdir7, writeFile as writeFile5 } from "node:fs/promises";
|
|
5531
|
+
import { dirname as dirname6 } from "node:path";
|
|
4848
5532
|
|
|
4849
5533
|
class ConfigStore {
|
|
4850
5534
|
path;
|
|
@@ -4855,8 +5539,8 @@ class ConfigStore {
|
|
|
4855
5539
|
return await loadConfig(this.path);
|
|
4856
5540
|
}
|
|
4857
5541
|
async save(config) {
|
|
4858
|
-
await
|
|
4859
|
-
await
|
|
5542
|
+
await mkdir7(dirname6(this.path), { recursive: true });
|
|
5543
|
+
await writeFile5(this.path, `${JSON.stringify(config, null, 2)}
|
|
4860
5544
|
`, "utf8");
|
|
4861
5545
|
}
|
|
4862
5546
|
async upsertWorkspace(name, cwd, description) {
|
|
@@ -4896,13 +5580,22 @@ class ConfigStore {
|
|
|
4896
5580
|
await this.save(config);
|
|
4897
5581
|
return config;
|
|
4898
5582
|
}
|
|
5583
|
+
async updateWechat(wechat) {
|
|
5584
|
+
const config = await this.load();
|
|
5585
|
+
config.wechat = {
|
|
5586
|
+
...config.wechat,
|
|
5587
|
+
...wechat
|
|
5588
|
+
};
|
|
5589
|
+
await this.save(config);
|
|
5590
|
+
return config;
|
|
5591
|
+
}
|
|
4899
5592
|
}
|
|
4900
5593
|
var init_config_store = __esm(() => {
|
|
4901
5594
|
init_load_config();
|
|
4902
5595
|
});
|
|
4903
5596
|
|
|
4904
5597
|
// src/config/ensure-config.ts
|
|
4905
|
-
import { readFile as
|
|
5598
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
4906
5599
|
async function ensureConfigExists(path11) {
|
|
4907
5600
|
try {
|
|
4908
5601
|
await loadConfig(path11);
|
|
@@ -4916,7 +5609,10 @@ async function ensureConfigExists(path11) {
|
|
|
4916
5609
|
}
|
|
4917
5610
|
async function loadDefaultConfigTemplate() {
|
|
4918
5611
|
const templatePath = new URL("../../config.example.json", import.meta.url);
|
|
4919
|
-
|
|
5612
|
+
return normalizeDefaultConfigTemplate(JSON.parse(await readFile6(templatePath, "utf8")));
|
|
5613
|
+
}
|
|
5614
|
+
function normalizeDefaultConfigTemplate(raw) {
|
|
5615
|
+
const template = parseConfig(raw);
|
|
4920
5616
|
return {
|
|
4921
5617
|
...template,
|
|
4922
5618
|
agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
|
|
@@ -5061,6 +5757,23 @@ class SessionService {
|
|
|
5061
5757
|
session.last_used_at = new Date().toISOString();
|
|
5062
5758
|
await this.persist();
|
|
5063
5759
|
}
|
|
5760
|
+
async setCurrentSessionReplyMode(chatKey, replyMode) {
|
|
5761
|
+
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
5762
|
+
if (!currentAlias) {
|
|
5763
|
+
throw new Error("no current session selected");
|
|
5764
|
+
}
|
|
5765
|
+
const session = this.state.sessions[currentAlias];
|
|
5766
|
+
if (!session) {
|
|
5767
|
+
throw new Error("no current session selected");
|
|
5768
|
+
}
|
|
5769
|
+
if (replyMode) {
|
|
5770
|
+
session.reply_mode = replyMode;
|
|
5771
|
+
} else {
|
|
5772
|
+
delete session.reply_mode;
|
|
5773
|
+
}
|
|
5774
|
+
session.last_used_at = new Date().toISOString();
|
|
5775
|
+
await this.persist();
|
|
5776
|
+
}
|
|
5064
5777
|
async getCurrentSession(chatKey) {
|
|
5065
5778
|
const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
|
|
5066
5779
|
if (!currentAlias) {
|
|
@@ -5085,6 +5798,13 @@ class SessionService {
|
|
|
5085
5798
|
}
|
|
5086
5799
|
toResolvedSession(session) {
|
|
5087
5800
|
const agentConfig = this.config.agents[session.agent];
|
|
5801
|
+
if (!agentConfig) {
|
|
5802
|
+
throw new Error(`session "${session.alias}" references agent "${session.agent}", but that agent is no longer registered`);
|
|
5803
|
+
}
|
|
5804
|
+
const workspaceConfig = this.config.workspaces[session.workspace];
|
|
5805
|
+
if (!workspaceConfig) {
|
|
5806
|
+
throw new Error(`session "${session.alias}" references workspace "${session.workspace}", but that workspace is no longer registered`);
|
|
5807
|
+
}
|
|
5088
5808
|
return {
|
|
5089
5809
|
alias: session.alias,
|
|
5090
5810
|
agent: session.agent,
|
|
@@ -5092,7 +5812,8 @@ class SessionService {
|
|
|
5092
5812
|
workspace: session.workspace,
|
|
5093
5813
|
transportSession: session.transport_session,
|
|
5094
5814
|
modeId: session.mode_id,
|
|
5095
|
-
|
|
5815
|
+
replyMode: session.reply_mode,
|
|
5816
|
+
cwd: workspaceConfig.cwd
|
|
5096
5817
|
};
|
|
5097
5818
|
}
|
|
5098
5819
|
async setSessionTransportAgentCommand(alias, transportAgentCommand) {
|
|
@@ -5124,6 +5845,7 @@ class SessionService {
|
|
|
5124
5845
|
transport_session: transportSession,
|
|
5125
5846
|
...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
|
|
5126
5847
|
mode_id: existingSession?.mode_id,
|
|
5848
|
+
reply_mode: existingSession?.reply_mode,
|
|
5127
5849
|
created_at: existingSession?.created_at ?? now,
|
|
5128
5850
|
last_used_at: now
|
|
5129
5851
|
};
|
|
@@ -5132,6 +5854,15 @@ class SessionService {
|
|
|
5132
5854
|
return this.toResolvedSession(session);
|
|
5133
5855
|
}
|
|
5134
5856
|
validateSession(alias, agent, workspace) {
|
|
5857
|
+
if (alias.trim().length === 0) {
|
|
5858
|
+
throw new Error("session alias must be a non-empty string");
|
|
5859
|
+
}
|
|
5860
|
+
if (agent.trim().length === 0) {
|
|
5861
|
+
throw new Error("agent must be a non-empty string");
|
|
5862
|
+
}
|
|
5863
|
+
if (workspace.trim().length === 0) {
|
|
5864
|
+
throw new Error("workspace must be a non-empty string");
|
|
5865
|
+
}
|
|
5135
5866
|
if (!this.config.workspaces[workspace]) {
|
|
5136
5867
|
throw new Error(`workspace "${workspace}" is not registered`);
|
|
5137
5868
|
}
|
|
@@ -5151,8 +5882,28 @@ function createEmptyState() {
|
|
|
5151
5882
|
}
|
|
5152
5883
|
|
|
5153
5884
|
// src/state/state-store.ts
|
|
5154
|
-
import { mkdir as
|
|
5155
|
-
import { dirname as
|
|
5885
|
+
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
|
|
5886
|
+
import { dirname as dirname7 } from "node:path";
|
|
5887
|
+
function isRecord2(value) {
|
|
5888
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5889
|
+
}
|
|
5890
|
+
function parseState(raw, path11) {
|
|
5891
|
+
if (!isRecord2(raw)) {
|
|
5892
|
+
throw new Error(`state file "${path11}" must contain a JSON object`);
|
|
5893
|
+
}
|
|
5894
|
+
const sessions = raw.sessions;
|
|
5895
|
+
if (!isRecord2(sessions)) {
|
|
5896
|
+
throw new Error(`state file "${path11}" must contain an object field "sessions"`);
|
|
5897
|
+
}
|
|
5898
|
+
const chatContexts = raw.chat_contexts;
|
|
5899
|
+
if (!isRecord2(chatContexts)) {
|
|
5900
|
+
throw new Error(`state file "${path11}" must contain an object field "chat_contexts"`);
|
|
5901
|
+
}
|
|
5902
|
+
return {
|
|
5903
|
+
sessions,
|
|
5904
|
+
chat_contexts: chatContexts
|
|
5905
|
+
};
|
|
5906
|
+
}
|
|
5156
5907
|
|
|
5157
5908
|
class StateStore {
|
|
5158
5909
|
path;
|
|
@@ -5161,11 +5912,19 @@ class StateStore {
|
|
|
5161
5912
|
}
|
|
5162
5913
|
async load() {
|
|
5163
5914
|
try {
|
|
5164
|
-
const content = await
|
|
5915
|
+
const content = await readFile7(this.path, "utf8");
|
|
5165
5916
|
if (content.trim() === "") {
|
|
5166
5917
|
return createEmptyState();
|
|
5167
5918
|
}
|
|
5168
|
-
|
|
5919
|
+
let parsed;
|
|
5920
|
+
try {
|
|
5921
|
+
parsed = JSON.parse(content);
|
|
5922
|
+
} catch (error) {
|
|
5923
|
+
throw new Error(`failed to parse state file "${this.path}"`, {
|
|
5924
|
+
cause: error
|
|
5925
|
+
});
|
|
5926
|
+
}
|
|
5927
|
+
return parseState(parsed, this.path);
|
|
5169
5928
|
} catch (error) {
|
|
5170
5929
|
if (error.code === "ENOENT") {
|
|
5171
5930
|
return createEmptyState();
|
|
@@ -5174,8 +5933,8 @@ class StateStore {
|
|
|
5174
5933
|
}
|
|
5175
5934
|
}
|
|
5176
5935
|
async save(state) {
|
|
5177
|
-
await
|
|
5178
|
-
await
|
|
5936
|
+
await mkdir8(dirname7(this.path), { recursive: true });
|
|
5937
|
+
await writeFile6(this.path, JSON.stringify(state, null, 2));
|
|
5179
5938
|
}
|
|
5180
5939
|
}
|
|
5181
5940
|
var init_state_store = () => {};
|
|
@@ -5187,12 +5946,17 @@ __export(exports_run_console, {
|
|
|
5187
5946
|
});
|
|
5188
5947
|
async function runConsole(paths, deps) {
|
|
5189
5948
|
const runtime = await deps.buildApp(paths);
|
|
5949
|
+
const consumerLock = deps.consumerLock ?? deps.consumerLockFactory?.(runtime);
|
|
5190
5950
|
const sdk = await deps.loadWeixinSdk();
|
|
5191
5951
|
const setIntervalFn = deps.setInterval ?? ((fn, delay) => setInterval(fn, delay));
|
|
5192
5952
|
const clearIntervalFn = deps.clearInterval ?? ((timer) => clearInterval(timer));
|
|
5193
5953
|
const addProcessListener = deps.addProcessListener ?? ((signal, handler) => process.on(signal, handler));
|
|
5194
5954
|
const removeProcessListener = deps.removeProcessListener ?? ((signal, handler) => process.off(signal, handler));
|
|
5955
|
+
const processPid = deps.processPid ?? process.pid;
|
|
5956
|
+
const now = deps.now ?? (() => new Date().toISOString());
|
|
5957
|
+
const hostname = deps.hostname ?? (() => "");
|
|
5195
5958
|
let heartbeatTimer = null;
|
|
5959
|
+
let consumerLockAcquired = false;
|
|
5196
5960
|
const shutdownController = new AbortController;
|
|
5197
5961
|
const signalHandler = () => {
|
|
5198
5962
|
shutdownController.abort();
|
|
@@ -5209,6 +5973,53 @@ async function runConsole(paths, deps) {
|
|
|
5209
5973
|
deps.daemonRuntime?.heartbeat().catch(() => {});
|
|
5210
5974
|
}, deps.heartbeatIntervalMs ?? 30000);
|
|
5211
5975
|
}
|
|
5976
|
+
if (consumerLock) {
|
|
5977
|
+
const lockMeta = {
|
|
5978
|
+
pid: processPid,
|
|
5979
|
+
mode: deps.daemonRuntime ? "daemon" : "foreground",
|
|
5980
|
+
startedAt: now(),
|
|
5981
|
+
configPath: paths.configPath,
|
|
5982
|
+
statePath: paths.statePath,
|
|
5983
|
+
hostname: hostname() || undefined
|
|
5984
|
+
};
|
|
5985
|
+
await runtime.logger.info("weixin.consumer_lock.acquire_attempt", "attempting to acquire weixin consumer lock", {
|
|
5986
|
+
pid: lockMeta.pid,
|
|
5987
|
+
mode: lockMeta.mode,
|
|
5988
|
+
configPath: lockMeta.configPath,
|
|
5989
|
+
statePath: lockMeta.statePath,
|
|
5990
|
+
hostname: lockMeta.hostname
|
|
5991
|
+
});
|
|
5992
|
+
try {
|
|
5993
|
+
await consumerLock.acquire(lockMeta);
|
|
5994
|
+
consumerLockAcquired = true;
|
|
5995
|
+
await runtime.logger.info("weixin.consumer_lock.acquired", "acquired weixin consumer lock", {
|
|
5996
|
+
pid: lockMeta.pid,
|
|
5997
|
+
mode: lockMeta.mode,
|
|
5998
|
+
configPath: lockMeta.configPath,
|
|
5999
|
+
statePath: lockMeta.statePath
|
|
6000
|
+
});
|
|
6001
|
+
} catch (error) {
|
|
6002
|
+
if (error instanceof ActiveWeixinConsumerLockError) {
|
|
6003
|
+
await runtime.logger.error("weixin.consumer_lock.acquire_failed", "weixin consumer lock is already held by another process", {
|
|
6004
|
+
conflictType: "active_lock_holder",
|
|
6005
|
+
activePid: error.existing.pid,
|
|
6006
|
+
activeMode: error.existing.mode,
|
|
6007
|
+
activeConfigPath: error.existing.configPath,
|
|
6008
|
+
activeStatePath: error.existing.statePath,
|
|
6009
|
+
requestedPid: lockMeta.pid,
|
|
6010
|
+
requestedMode: lockMeta.mode
|
|
6011
|
+
});
|
|
6012
|
+
} else {
|
|
6013
|
+
await runtime.logger.error("weixin.consumer_lock.acquire_failed", "failed to acquire weixin consumer lock", {
|
|
6014
|
+
conflictType: deps.daemonRuntime ? "daemon_startup_lock_failure" : "foreground_startup_lock_failure",
|
|
6015
|
+
requestedPid: lockMeta.pid,
|
|
6016
|
+
requestedMode: lockMeta.mode,
|
|
6017
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6018
|
+
});
|
|
6019
|
+
}
|
|
6020
|
+
throw error;
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
5212
6023
|
if (!sdk.isLoggedIn()) {
|
|
5213
6024
|
console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
|
|
5214
6025
|
await sdk.login();
|
|
@@ -5229,17 +6040,30 @@ async function runConsole(paths, deps) {
|
|
|
5229
6040
|
if (deps.daemonRuntime) {
|
|
5230
6041
|
await deps.daemonRuntime.stop();
|
|
5231
6042
|
}
|
|
6043
|
+
if (consumerLockAcquired) {
|
|
6044
|
+
await consumerLock?.release();
|
|
6045
|
+
await runtime.logger.info("weixin.consumer_lock.released", "released weixin consumer lock", {
|
|
6046
|
+
pid: processPid
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
5232
6049
|
if (disposeError) {
|
|
5233
6050
|
throw disposeError;
|
|
5234
6051
|
}
|
|
5235
6052
|
}
|
|
5236
6053
|
}
|
|
6054
|
+
var init_run_console = __esm(() => {
|
|
6055
|
+
init_consumer_lock();
|
|
6056
|
+
});
|
|
5237
6057
|
|
|
5238
6058
|
// src/transport/acpx-bridge/acpx-bridge-protocol.ts
|
|
5239
6059
|
function encodeBridgeRequest(request) {
|
|
5240
6060
|
return `${JSON.stringify(request)}
|
|
5241
6061
|
`;
|
|
5242
6062
|
}
|
|
6063
|
+
function encodeBridgePromptSegmentEvent(event) {
|
|
6064
|
+
return `${JSON.stringify(event)}
|
|
6065
|
+
`;
|
|
6066
|
+
}
|
|
5243
6067
|
|
|
5244
6068
|
// src/transport/acpx-bridge/acpx-bridge-client.ts
|
|
5245
6069
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -5250,30 +6074,59 @@ class AcpxBridgeClient {
|
|
|
5250
6074
|
writeLine;
|
|
5251
6075
|
nextId = 1;
|
|
5252
6076
|
pending = new Map;
|
|
6077
|
+
terminalError = null;
|
|
5253
6078
|
constructor(writeLine) {
|
|
5254
6079
|
this.writeLine = writeLine;
|
|
5255
6080
|
}
|
|
5256
|
-
request(method, params) {
|
|
6081
|
+
request(method, params, onEvent) {
|
|
6082
|
+
if (this.terminalError) {
|
|
6083
|
+
return Promise.reject(this.terminalError);
|
|
6084
|
+
}
|
|
5257
6085
|
const id = String(this.nextId);
|
|
5258
6086
|
this.nextId += 1;
|
|
5259
6087
|
return awaitable((resolve2, reject) => {
|
|
5260
6088
|
this.pending.set(id, {
|
|
5261
6089
|
resolve: (value) => resolve2(value),
|
|
5262
|
-
reject
|
|
6090
|
+
reject,
|
|
6091
|
+
onEvent
|
|
5263
6092
|
});
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
6093
|
+
try {
|
|
6094
|
+
const didWrite = this.writeLine(encodeBridgeRequest({
|
|
6095
|
+
id,
|
|
6096
|
+
method,
|
|
6097
|
+
params
|
|
6098
|
+
}));
|
|
6099
|
+
if (didWrite === false) {
|
|
6100
|
+
this.pending.delete(id);
|
|
6101
|
+
reject(new Error("bridge write buffer is full"));
|
|
6102
|
+
}
|
|
6103
|
+
} catch (error) {
|
|
6104
|
+
this.pending.delete(id);
|
|
6105
|
+
reject(error);
|
|
6106
|
+
}
|
|
5269
6107
|
});
|
|
5270
6108
|
}
|
|
5271
6109
|
handleLine(line) {
|
|
5272
|
-
|
|
5273
|
-
|
|
6110
|
+
let message;
|
|
6111
|
+
try {
|
|
6112
|
+
message = JSON.parse(line);
|
|
6113
|
+
} catch {
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
const pending = this.pending.get(message.id);
|
|
5274
6117
|
if (!pending) {
|
|
5275
6118
|
return;
|
|
5276
6119
|
}
|
|
6120
|
+
if ("event" in message) {
|
|
6121
|
+
if (message.event === "prompt.segment") {
|
|
6122
|
+
pending.onEvent?.({
|
|
6123
|
+
type: "prompt.segment",
|
|
6124
|
+
text: message.text
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
return;
|
|
6128
|
+
}
|
|
6129
|
+
const response = message;
|
|
5277
6130
|
this.pending.delete(response.id);
|
|
5278
6131
|
if (response.ok) {
|
|
5279
6132
|
pending.resolve(response.result);
|
|
@@ -5290,6 +6143,7 @@ class AcpxBridgeClient {
|
|
|
5290
6143
|
pending.reject(new Error(response.error.message));
|
|
5291
6144
|
}
|
|
5292
6145
|
handleExit(error) {
|
|
6146
|
+
this.terminalError = error;
|
|
5293
6147
|
const pendingRequests = [...this.pending.values()];
|
|
5294
6148
|
this.pending.clear();
|
|
5295
6149
|
for (const pending of pendingRequests) {
|
|
@@ -5321,13 +6175,11 @@ async function spawnAcpxBridgeClient(options = {}) {
|
|
|
5321
6175
|
...process.env,
|
|
5322
6176
|
WEACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
|
|
5323
6177
|
WEACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
|
|
5324
|
-
WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "
|
|
6178
|
+
WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny"
|
|
5325
6179
|
},
|
|
5326
6180
|
stdio: ["pipe", "pipe", "inherit"]
|
|
5327
6181
|
});
|
|
5328
|
-
const client = new AcpxBridgeClient((line) =>
|
|
5329
|
-
child.stdin.write(line);
|
|
5330
|
-
});
|
|
6182
|
+
const client = new AcpxBridgeClient((line) => child.stdin.write(line));
|
|
5331
6183
|
const output = createInterface({
|
|
5332
6184
|
input: child.stdout,
|
|
5333
6185
|
crlfDelay: Infinity
|
|
@@ -5374,10 +6226,14 @@ class AcpxBridgeTransport {
|
|
|
5374
6226
|
async ensureSession(session) {
|
|
5375
6227
|
await this.client.request("ensureSession", this.toParams(session));
|
|
5376
6228
|
}
|
|
5377
|
-
async prompt(session, text,
|
|
6229
|
+
async prompt(session, text, reply) {
|
|
5378
6230
|
return await this.client.request("prompt", {
|
|
5379
6231
|
...this.toParams(session),
|
|
5380
6232
|
text
|
|
6233
|
+
}, (event) => {
|
|
6234
|
+
if (event.type === "prompt.segment") {
|
|
6235
|
+
reply?.(event.text);
|
|
6236
|
+
}
|
|
5381
6237
|
});
|
|
5382
6238
|
}
|
|
5383
6239
|
async setMode(session, modeId) {
|
|
@@ -5393,6 +6249,9 @@ class AcpxBridgeTransport {
|
|
|
5393
6249
|
const result = await this.client.request("hasSession", this.toParams(session));
|
|
5394
6250
|
return result.exists;
|
|
5395
6251
|
}
|
|
6252
|
+
async updatePermissionPolicy(policy) {
|
|
6253
|
+
await this.client.request("updatePermissionPolicy", { ...policy });
|
|
6254
|
+
}
|
|
5396
6255
|
async dispose() {
|
|
5397
6256
|
await this.client.dispose?.();
|
|
5398
6257
|
}
|
|
@@ -5481,12 +6340,12 @@ function parseStreamingChunks(state, line) {
|
|
|
5481
6340
|
|
|
5482
6341
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
5483
6342
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
5484
|
-
import { dirname as
|
|
6343
|
+
import { dirname as dirname8, join as join4 } from "node:path";
|
|
5485
6344
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
5486
6345
|
if (platform === "win32") {
|
|
5487
6346
|
return null;
|
|
5488
6347
|
}
|
|
5489
|
-
return
|
|
6348
|
+
return join4(dirname8(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
5490
6349
|
}
|
|
5491
6350
|
async function ensureNodePtyHelperExecutable(helperPath, chmod = chmodFs) {
|
|
5492
6351
|
if (!helperPath) {
|
|
@@ -5574,7 +6433,7 @@ class AcpxCliTransport {
|
|
|
5574
6433
|
this.command = options.command ?? "acpx";
|
|
5575
6434
|
this.sessionInitTimeoutMs = options.sessionInitTimeoutMs ?? 120000;
|
|
5576
6435
|
this.permissionMode = options.permissionMode ?? "approve-all";
|
|
5577
|
-
this.nonInteractivePermissions = options.nonInteractivePermissions ?? "
|
|
6436
|
+
this.nonInteractivePermissions = options.nonInteractivePermissions ?? "deny";
|
|
5578
6437
|
this.runCommand = runCommand;
|
|
5579
6438
|
this.runPtyCommand = runPtyCommand;
|
|
5580
6439
|
}
|
|
@@ -5618,6 +6477,10 @@ class AcpxCliTransport {
|
|
|
5618
6477
|
message: output.trim()
|
|
5619
6478
|
};
|
|
5620
6479
|
}
|
|
6480
|
+
async updatePermissionPolicy(policy) {
|
|
6481
|
+
this.permissionMode = policy.permissionMode;
|
|
6482
|
+
this.nonInteractivePermissions = policy.nonInteractivePermissions;
|
|
6483
|
+
}
|
|
5621
6484
|
async hasSession(session) {
|
|
5622
6485
|
const result = await this.runCommand(this.command, this.buildArgs(session, [
|
|
5623
6486
|
"sessions",
|
|
@@ -5774,8 +6637,8 @@ __export(exports_main, {
|
|
|
5774
6637
|
main: () => main2,
|
|
5775
6638
|
buildApp: () => buildApp
|
|
5776
6639
|
});
|
|
5777
|
-
import { homedir as
|
|
5778
|
-
import { dirname as
|
|
6640
|
+
import { homedir as homedir5 } from "node:os";
|
|
6641
|
+
import { dirname as dirname9, join as join5 } from "node:path";
|
|
5779
6642
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5780
6643
|
async function buildApp(paths, deps = {}) {
|
|
5781
6644
|
await ensureConfigExists(paths.configPath);
|
|
@@ -5837,7 +6700,7 @@ async function main2() {
|
|
|
5837
6700
|
}
|
|
5838
6701
|
}
|
|
5839
6702
|
function resolveRuntimePaths() {
|
|
5840
|
-
const home = process.env.HOME ??
|
|
6703
|
+
const home = process.env.HOME ?? homedir5();
|
|
5841
6704
|
if (!home) {
|
|
5842
6705
|
throw new Error("Unable to resolve the current user home directory");
|
|
5843
6706
|
}
|
|
@@ -5853,9 +6716,9 @@ function resolveBridgeEntryPath() {
|
|
|
5853
6716
|
return fileURLToPath3(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
5854
6717
|
}
|
|
5855
6718
|
function resolveAppLogPath(configPath) {
|
|
5856
|
-
const rootDir =
|
|
5857
|
-
const runtimeDir =
|
|
5858
|
-
return
|
|
6719
|
+
const rootDir = dirname9(configPath);
|
|
6720
|
+
const runtimeDir = join5(rootDir, "runtime");
|
|
6721
|
+
return join5(runtimeDir, "app.log");
|
|
5859
6722
|
}
|
|
5860
6723
|
var init_main = __esm(async () => {
|
|
5861
6724
|
init_command_router();
|
|
@@ -5867,6 +6730,7 @@ var init_main = __esm(async () => {
|
|
|
5867
6730
|
init_app_logger();
|
|
5868
6731
|
init_session_service();
|
|
5869
6732
|
init_state_store();
|
|
6733
|
+
init_run_console();
|
|
5870
6734
|
init_acpx_bridge_client();
|
|
5871
6735
|
init_acpx_cli_transport();
|
|
5872
6736
|
init_weixin_sdk();
|
|
@@ -5874,7 +6738,7 @@ var init_main = __esm(async () => {
|
|
|
5874
6738
|
});
|
|
5875
6739
|
|
|
5876
6740
|
// src/cli.ts
|
|
5877
|
-
import { homedir as
|
|
6741
|
+
import { homedir as homedir6 } from "node:os";
|
|
5878
6742
|
import { sep } from "node:path";
|
|
5879
6743
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5880
6744
|
|
|
@@ -5955,7 +6819,7 @@ class DaemonController {
|
|
|
5955
6819
|
return { state: "stopped", stale: true };
|
|
5956
6820
|
}
|
|
5957
6821
|
if (!status) {
|
|
5958
|
-
return { state: "
|
|
6822
|
+
return { state: "indeterminate", pid, reason: "missing-status" };
|
|
5959
6823
|
}
|
|
5960
6824
|
return {
|
|
5961
6825
|
state: "running",
|
|
@@ -5968,6 +6832,9 @@ class DaemonController {
|
|
|
5968
6832
|
if (current.state === "running") {
|
|
5969
6833
|
return { state: "already-running", pid: current.pid };
|
|
5970
6834
|
}
|
|
6835
|
+
if (current.state === "indeterminate") {
|
|
6836
|
+
throw new Error(`weacpx daemon process is already running (pid ${current.pid}) but status metadata is missing`);
|
|
6837
|
+
}
|
|
5971
6838
|
await this.statusStore.clear();
|
|
5972
6839
|
const pid = await this.deps.spawnDetached();
|
|
5973
6840
|
await this.writePid(pid);
|
|
@@ -6175,27 +7042,30 @@ async function spawnWindowsHiddenProcess(request) {
|
|
|
6175
7042
|
async function defaultTerminateProcess(pid) {
|
|
6176
7043
|
await terminateProcessTree(pid);
|
|
6177
7044
|
}
|
|
6178
|
-
async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand) {
|
|
7045
|
+
async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
|
|
7046
|
+
process.kill(targetPid, signal);
|
|
7047
|
+
}, isProcessRunning = defaultIsProcessRunning) {
|
|
6179
7048
|
if (platform === "win32") {
|
|
6180
7049
|
try {
|
|
6181
7050
|
await runCommand("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
6182
7051
|
} catch {}
|
|
6183
7052
|
return;
|
|
6184
7053
|
}
|
|
7054
|
+
const targetPid = pid > 0 ? -pid : pid;
|
|
6185
7055
|
try {
|
|
6186
|
-
|
|
7056
|
+
killProcess(targetPid, "SIGTERM");
|
|
6187
7057
|
} catch {
|
|
6188
7058
|
return;
|
|
6189
7059
|
}
|
|
6190
7060
|
const deadline = Date.now() + 5000;
|
|
6191
7061
|
while (Date.now() < deadline) {
|
|
6192
|
-
if (!
|
|
7062
|
+
if (!isProcessRunning(targetPid)) {
|
|
6193
7063
|
return;
|
|
6194
7064
|
}
|
|
6195
7065
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6196
7066
|
}
|
|
6197
7067
|
try {
|
|
6198
|
-
|
|
7068
|
+
killProcess(targetPid, "SIGKILL");
|
|
6199
7069
|
} catch {}
|
|
6200
7070
|
}
|
|
6201
7071
|
async function defaultRunProcessCommand(command, args) {
|
|
@@ -6270,6 +7140,7 @@ class DaemonRuntime {
|
|
|
6270
7140
|
}
|
|
6271
7141
|
|
|
6272
7142
|
// src/cli.ts
|
|
7143
|
+
init_consumer_lock();
|
|
6273
7144
|
var HELP_LINES = [
|
|
6274
7145
|
"用法:",
|
|
6275
7146
|
"weacpx login - 微信登录",
|
|
@@ -6306,6 +7177,11 @@ async function runCli(args, deps = {}) {
|
|
|
6306
7177
|
}
|
|
6307
7178
|
case "status": {
|
|
6308
7179
|
const status = await controller.getStatus();
|
|
7180
|
+
if (status.state === "indeterminate") {
|
|
7181
|
+
print("weacpx 进程仍在运行,但状态元数据缺失");
|
|
7182
|
+
print(`PID: ${status.pid}`);
|
|
7183
|
+
return 1;
|
|
7184
|
+
}
|
|
6309
7185
|
if (status.state !== "running") {
|
|
6310
7186
|
print("weacpx 未运行");
|
|
6311
7187
|
return 0;
|
|
@@ -6349,7 +7225,7 @@ async function defaultRun() {
|
|
|
6349
7225
|
const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2 }, { loadWeixinSdk: loadWeixinSdk2 }, { runConsole: runConsole2 }] = await Promise.all([
|
|
6350
7226
|
init_main().then(() => exports_main),
|
|
6351
7227
|
Promise.resolve().then(() => (init_weixin_sdk(), exports_weixin_sdk)),
|
|
6352
|
-
Promise.resolve().then(() => exports_run_console)
|
|
7228
|
+
Promise.resolve().then(() => (init_run_console(), exports_run_console))
|
|
6353
7229
|
]);
|
|
6354
7230
|
const runtimePaths = resolveRuntimePaths2();
|
|
6355
7231
|
const daemonPaths = resolveDaemonPaths({ home: requireHome() });
|
|
@@ -6359,7 +7235,13 @@ async function defaultRun() {
|
|
|
6359
7235
|
defaultLoggingLevel: resolveCliEntryPath().includes(`${sep}src${sep}`) ? "debug" : "info"
|
|
6360
7236
|
}),
|
|
6361
7237
|
loadWeixinSdk: loadWeixinSdk2,
|
|
6362
|
-
daemonRuntime
|
|
7238
|
+
daemonRuntime,
|
|
7239
|
+
consumerLockFactory: (runtime) => createWeixinConsumerLock({
|
|
7240
|
+
lockFilePath: `${daemonPaths.runtimeDir}${sep}weixin-consumer.lock.json`,
|
|
7241
|
+
onDiagnostic: async (event, context) => {
|
|
7242
|
+
await runtime.logger.info(`weixin.consumer_lock.${event}`, "weixin consumer lock diagnostic", context);
|
|
7243
|
+
}
|
|
7244
|
+
})
|
|
6363
7245
|
});
|
|
6364
7246
|
}
|
|
6365
7247
|
function createDefaultController() {
|
|
@@ -6372,7 +7254,7 @@ function createDefaultController() {
|
|
|
6372
7254
|
});
|
|
6373
7255
|
}
|
|
6374
7256
|
function requireHome() {
|
|
6375
|
-
const home = process.env.HOME ??
|
|
7257
|
+
const home = process.env.HOME ?? homedir6();
|
|
6376
7258
|
if (!home) {
|
|
6377
7259
|
throw new Error("Unable to resolve the current user home directory");
|
|
6378
7260
|
}
|