weacpx 0.4.6 → 0.4.9
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 +6 -5
- package/dist/bridge/bridge-main.js +78 -4
- package/dist/channels/types.d.ts +4 -0
- package/dist/cli.js +1775 -491
- package/dist/config/types.d.ts +2 -0
- package/dist/orchestration/orchestration-types.d.ts +7 -1
- package/dist/weixin/agent/interface.d.ts +2 -0
- package/dist/weixin/api/api.d.ts +28 -0
- package/dist/weixin/api/session-guard.d.ts +2 -0
- package/dist/weixin/api/types.d.ts +2 -0
- package/dist/weixin/auth/accounts.d.ts +8 -1
- package/dist/weixin/auth/login-qr.d.ts +6 -0
- package/dist/weixin/index.d.ts +1 -1
- package/dist/weixin/messaging/inbound.d.ts +18 -1
- package/dist/weixin/messaging/markdown-filter.d.ts +45 -0
- package/dist/weixin/messaging/send.d.ts +3 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2201,6 +2201,11 @@ function parseConfig(raw, options = {}) {
|
|
|
2201
2201
|
if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
|
|
2202
2202
|
throw new Error("transport.nonInteractivePermissions must be deny or fail");
|
|
2203
2203
|
}
|
|
2204
|
+
if ("permissionPolicy" in transport && transport.permissionPolicy !== undefined) {
|
|
2205
|
+
if (typeof transport.permissionPolicy !== "string" || transport.permissionPolicy.trim().length === 0) {
|
|
2206
|
+
throw new Error("transport.permissionPolicy must be a non-empty string");
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2204
2209
|
if (!isRecord(raw.agents)) {
|
|
2205
2210
|
throw new Error("agents must be an object");
|
|
2206
2211
|
}
|
|
@@ -2294,6 +2299,7 @@ function parseConfig(raw, options = {}) {
|
|
|
2294
2299
|
transport: {
|
|
2295
2300
|
...typeof transport.command === "string" ? { command: transport.command } : {},
|
|
2296
2301
|
...typeof transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: transport.sessionInitTimeoutMs } : {},
|
|
2302
|
+
...typeof transport.permissionPolicy === "string" ? { permissionPolicy: transport.permissionPolicy } : {},
|
|
2297
2303
|
type: transportType,
|
|
2298
2304
|
permissionMode,
|
|
2299
2305
|
nonInteractivePermissions
|
|
@@ -2418,7 +2424,8 @@ function parseOrchestrationConfig(raw) {
|
|
|
2418
2424
|
allowWorkerChainedRequests: raw.allowWorkerChainedRequests === true,
|
|
2419
2425
|
allowedAgentRequestTargets: Array.isArray(raw.allowedAgentRequestTargets) ? raw.allowedAgentRequestTargets.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestTargets],
|
|
2420
2426
|
allowedAgentRequestRoles: Array.isArray(raw.allowedAgentRequestRoles) ? raw.allowedAgentRequestRoles.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestRoles],
|
|
2421
|
-
progressHeartbeatSeconds: typeof raw.progressHeartbeatSeconds === "number" && Number.isFinite(raw.progressHeartbeatSeconds) ? raw.progressHeartbeatSeconds : DEFAULT_ORCHESTRATION_CONFIG.progressHeartbeatSeconds
|
|
2427
|
+
progressHeartbeatSeconds: typeof raw.progressHeartbeatSeconds === "number" && Number.isFinite(raw.progressHeartbeatSeconds) ? raw.progressHeartbeatSeconds : DEFAULT_ORCHESTRATION_CONFIG.progressHeartbeatSeconds,
|
|
2428
|
+
maxParallelTasksPerAgent: typeof raw.maxParallelTasksPerAgent === "number" && Number.isFinite(raw.maxParallelTasksPerAgent) && raw.maxParallelTasksPerAgent >= 1 ? Math.floor(raw.maxParallelTasksPerAgent) : DEFAULT_ORCHESTRATION_CONFIG.maxParallelTasksPerAgent
|
|
2422
2429
|
};
|
|
2423
2430
|
}
|
|
2424
2431
|
var DEFAULT_PERF_LOG_CONFIG, DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_CHANNEL_CONFIG, DEFAULT_ORCHESTRATION_CONFIG;
|
|
@@ -2446,7 +2453,8 @@ var init_load_config = __esm(() => {
|
|
|
2446
2453
|
allowWorkerChainedRequests: false,
|
|
2447
2454
|
allowedAgentRequestTargets: [],
|
|
2448
2455
|
allowedAgentRequestRoles: [],
|
|
2449
|
-
progressHeartbeatSeconds: 300
|
|
2456
|
+
progressHeartbeatSeconds: 300,
|
|
2457
|
+
maxParallelTasksPerAgent: 3
|
|
2450
2458
|
};
|
|
2451
2459
|
});
|
|
2452
2460
|
|
|
@@ -3096,7 +3104,7 @@ var init_orchestration_ipc = () => {};
|
|
|
3096
3104
|
// src/daemon/daemon-files.ts
|
|
3097
3105
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
3098
3106
|
function resolveDaemonPaths(options) {
|
|
3099
|
-
const runtimeDir = options.runtimeDir ?? join2(options.home, ".weacpx", "runtime");
|
|
3107
|
+
const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join2(options.home, ".weacpx", "runtime"));
|
|
3100
3108
|
return {
|
|
3101
3109
|
runtimeDir,
|
|
3102
3110
|
pidFile: join2(runtimeDir, "daemon.pid"),
|
|
@@ -9639,6 +9647,34 @@ var init_quota_errors = __esm(() => {
|
|
|
9639
9647
|
};
|
|
9640
9648
|
});
|
|
9641
9649
|
|
|
9650
|
+
// src/commands/workspace-name.ts
|
|
9651
|
+
function sanitizeWorkspaceName(input, fallback = "workspace") {
|
|
9652
|
+
const sanitized = input.trim().replace(UNSAFE_RUN_RE, "-").replace(TRIM_DASHES_RE, "");
|
|
9653
|
+
return sanitized.length > 0 ? sanitized : fallback;
|
|
9654
|
+
}
|
|
9655
|
+
function allocateWorkspaceName(base, existing) {
|
|
9656
|
+
if (!Object.prototype.hasOwnProperty.call(existing, base))
|
|
9657
|
+
return base;
|
|
9658
|
+
let suffix = 2;
|
|
9659
|
+
while (Object.prototype.hasOwnProperty.call(existing, `${base}-${suffix}`))
|
|
9660
|
+
suffix += 1;
|
|
9661
|
+
return `${base}-${suffix}`;
|
|
9662
|
+
}
|
|
9663
|
+
function isWorkspaceNameValid(input) {
|
|
9664
|
+
return VALID_WORKSPACE_NAME_RE.test(input);
|
|
9665
|
+
}
|
|
9666
|
+
function quoteWorkspaceNameIfNeeded(input) {
|
|
9667
|
+
if (isWorkspaceNameValid(input))
|
|
9668
|
+
return input;
|
|
9669
|
+
return `"${input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
9670
|
+
}
|
|
9671
|
+
var VALID_WORKSPACE_NAME_RE, UNSAFE_RUN_RE, TRIM_DASHES_RE;
|
|
9672
|
+
var init_workspace_name = __esm(() => {
|
|
9673
|
+
VALID_WORKSPACE_NAME_RE = /^[a-zA-Z0-9._-]+$/;
|
|
9674
|
+
UNSAFE_RUN_RE = /[^a-zA-Z0-9._-]+/g;
|
|
9675
|
+
TRIM_DASHES_RE = /^-+|-+$/g;
|
|
9676
|
+
});
|
|
9677
|
+
|
|
9642
9678
|
// src/orchestration/orchestration-types.ts
|
|
9643
9679
|
function createEmptyOrchestrationState() {
|
|
9644
9680
|
return {
|
|
@@ -9677,7 +9713,7 @@ function isOptionalBoolean(value) {
|
|
|
9677
9713
|
return value === undefined || typeof value === "boolean";
|
|
9678
9714
|
}
|
|
9679
9715
|
function isTaskStatus(value) {
|
|
9680
|
-
return value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
|
|
9716
|
+
return value === "needs_confirmation" || value === "queued" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
|
|
9681
9717
|
}
|
|
9682
9718
|
function isSourceKind(value) {
|
|
9683
9719
|
return value === "human" || value === "coordinator" || value === "worker";
|
|
@@ -9713,7 +9749,7 @@ function isTaskRecord(value) {
|
|
|
9713
9749
|
if (!isRecord2(value)) {
|
|
9714
9750
|
return false;
|
|
9715
9751
|
}
|
|
9716
|
-
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.lastProgressSummary) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending)) && isOptionalNumber(value.eventSeq) && (value.events === undefined || Array.isArray(value.events) && value.events.every(isTaskEventRecord));
|
|
9752
|
+
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalBoolean(value.ephemeralWorkerSession) && isOptionalBoolean(value.ephemeralWorkerSessionClosed) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.lastProgressSummary) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending)) && isOptionalNumber(value.eventSeq) && (value.events === undefined || Array.isArray(value.events) && value.events.every(isTaskEventRecord));
|
|
9717
9753
|
}
|
|
9718
9754
|
function isExternalCoordinatorRecord(value) {
|
|
9719
9755
|
if (!isRecord2(value)) {
|
|
@@ -9725,7 +9761,7 @@ function isWorkerBindingRecord(value) {
|
|
|
9725
9761
|
if (!isRecord2(value)) {
|
|
9726
9762
|
return false;
|
|
9727
9763
|
}
|
|
9728
|
-
return isString(value.sourceHandle) && isString(value.coordinatorSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role);
|
|
9764
|
+
return isString(value.sourceHandle) && isString(value.coordinatorSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isOptionalBoolean(value.ephemeral);
|
|
9729
9765
|
}
|
|
9730
9766
|
function isGroupRecord(value) {
|
|
9731
9767
|
if (!isRecord2(value)) {
|
|
@@ -9981,12 +10017,25 @@ var init_state_store = __esm(() => {
|
|
|
9981
10017
|
import { mkdir as mkdir6, writeFile as writeFile5 } from "node:fs/promises";
|
|
9982
10018
|
import { homedir as homedir3 } from "node:os";
|
|
9983
10019
|
import { join as join3 } from "node:path";
|
|
10020
|
+
function coerceMissing(value) {
|
|
10021
|
+
if (value === undefined)
|
|
10022
|
+
return;
|
|
10023
|
+
const trimmed = value.trim();
|
|
10024
|
+
if (!trimmed)
|
|
10025
|
+
return;
|
|
10026
|
+
const lower = trimmed.toLowerCase();
|
|
10027
|
+
if (lower === "undefined" || lower === "null")
|
|
10028
|
+
return;
|
|
10029
|
+
return trimmed;
|
|
10030
|
+
}
|
|
9984
10031
|
function resolvePluginHome(input = {}) {
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
10032
|
+
const explicit = coerceMissing(input.pluginHome);
|
|
10033
|
+
if (explicit)
|
|
10034
|
+
return explicit;
|
|
10035
|
+
const envOverride = coerceMissing(process.env.WEACPX_PLUGIN_HOME);
|
|
10036
|
+
if (envOverride)
|
|
10037
|
+
return envOverride;
|
|
10038
|
+
const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
|
|
9990
10039
|
return join3(home, ".weacpx", "plugins");
|
|
9991
10040
|
}
|
|
9992
10041
|
async function ensurePluginHome(pluginHome) {
|
|
@@ -10044,6 +10093,31 @@ function resolveWeixinStateDir() {
|
|
|
10044
10093
|
function resolveAccountIndexPath() {
|
|
10045
10094
|
return path4.join(resolveWeixinStateDir(), "accounts.json");
|
|
10046
10095
|
}
|
|
10096
|
+
function listAccountFileIds() {
|
|
10097
|
+
const dir = resolveAccountsDir();
|
|
10098
|
+
try {
|
|
10099
|
+
if (!fs3.existsSync(dir))
|
|
10100
|
+
return [];
|
|
10101
|
+
return fs3.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => ({
|
|
10102
|
+
id: entry.name.slice(0, -5),
|
|
10103
|
+
data: readAccountFile(path4.join(dir, entry.name))
|
|
10104
|
+
})).filter((entry) => entry.id.trim() !== "" && Boolean(entry.data?.token?.trim())).map((entry) => entry.id).sort();
|
|
10105
|
+
} catch {
|
|
10106
|
+
return [];
|
|
10107
|
+
}
|
|
10108
|
+
}
|
|
10109
|
+
function uniqueAccountIds(ids) {
|
|
10110
|
+
const seen = new Set;
|
|
10111
|
+
const result = [];
|
|
10112
|
+
for (const raw of ids) {
|
|
10113
|
+
const id = raw.trim();
|
|
10114
|
+
if (!id || seen.has(id))
|
|
10115
|
+
continue;
|
|
10116
|
+
seen.add(id);
|
|
10117
|
+
result.push(id);
|
|
10118
|
+
}
|
|
10119
|
+
return result;
|
|
10120
|
+
}
|
|
10047
10121
|
function listIndexedWeixinAccountIds() {
|
|
10048
10122
|
const filePath = resolveAccountIndexPath();
|
|
10049
10123
|
try {
|
|
@@ -10053,7 +10127,7 @@ function listIndexedWeixinAccountIds() {
|
|
|
10053
10127
|
const parsed = JSON.parse(raw);
|
|
10054
10128
|
if (!Array.isArray(parsed))
|
|
10055
10129
|
return [];
|
|
10056
|
-
return parsed.filter((id) => typeof id === "string" && id.trim() !== "");
|
|
10130
|
+
return uniqueAccountIds(parsed.filter((id) => typeof id === "string" && id.trim() !== ""));
|
|
10057
10131
|
} catch {
|
|
10058
10132
|
return [];
|
|
10059
10133
|
}
|
|
@@ -10128,7 +10202,10 @@ function clearWeixinAccount(accountId) {
|
|
|
10128
10202
|
} catch {}
|
|
10129
10203
|
}
|
|
10130
10204
|
function clearAllWeixinAccounts() {
|
|
10131
|
-
const ids =
|
|
10205
|
+
const ids = uniqueAccountIds([
|
|
10206
|
+
...listIndexedWeixinAccountIds(),
|
|
10207
|
+
...listAccountFileIds()
|
|
10208
|
+
]);
|
|
10132
10209
|
for (const id of ids) {
|
|
10133
10210
|
clearWeixinAccount(id);
|
|
10134
10211
|
}
|
|
@@ -10168,8 +10245,33 @@ function loadConfigRouteTag(accountId) {
|
|
|
10168
10245
|
return;
|
|
10169
10246
|
}
|
|
10170
10247
|
}
|
|
10248
|
+
function loadConfigBotAgent(accountId) {
|
|
10249
|
+
try {
|
|
10250
|
+
const configPath = resolveConfigPath();
|
|
10251
|
+
if (!fs3.existsSync(configPath))
|
|
10252
|
+
return;
|
|
10253
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
10254
|
+
const cfg = JSON.parse(raw);
|
|
10255
|
+
const channels = cfg.channels;
|
|
10256
|
+
const section = channels?.["openclaw-weixin"];
|
|
10257
|
+
if (!section)
|
|
10258
|
+
return;
|
|
10259
|
+
if (accountId) {
|
|
10260
|
+
const accounts = section.accounts;
|
|
10261
|
+
const agent = accounts?.[accountId]?.botAgent;
|
|
10262
|
+
if (typeof agent === "string" && agent.trim())
|
|
10263
|
+
return agent.trim();
|
|
10264
|
+
}
|
|
10265
|
+
return typeof section.botAgent === "string" && section.botAgent.trim() ? section.botAgent.trim() : undefined;
|
|
10266
|
+
} catch {
|
|
10267
|
+
return;
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10171
10270
|
function listWeixinAccountIds() {
|
|
10172
|
-
|
|
10271
|
+
const indexed = listIndexedWeixinAccountIds();
|
|
10272
|
+
if (indexed.length > 0)
|
|
10273
|
+
return indexed;
|
|
10274
|
+
return listAccountFileIds();
|
|
10173
10275
|
}
|
|
10174
10276
|
function resolveWeixinAccount(accountId) {
|
|
10175
10277
|
const raw = accountId?.trim();
|
|
@@ -10453,8 +10555,83 @@ var init_send_errors = __esm(() => {
|
|
|
10453
10555
|
|
|
10454
10556
|
// src/weixin/api/api.ts
|
|
10455
10557
|
import crypto from "node:crypto";
|
|
10558
|
+
function buildClientVersion(version2) {
|
|
10559
|
+
const parts = version2.split(".").map((p) => parseInt(p, 10));
|
|
10560
|
+
const major = parts[0] ?? 0;
|
|
10561
|
+
const minor = parts[1] ?? 0;
|
|
10562
|
+
const patch = parts[2] ?? 0;
|
|
10563
|
+
return (major & 255) << 16 | (minor & 255) << 8 | patch & 255;
|
|
10564
|
+
}
|
|
10565
|
+
function sanitizeBotAgent(raw) {
|
|
10566
|
+
if (!raw || typeof raw !== "string")
|
|
10567
|
+
return DEFAULT_BOT_AGENT;
|
|
10568
|
+
const trimmed = raw.trim();
|
|
10569
|
+
if (!trimmed)
|
|
10570
|
+
return DEFAULT_BOT_AGENT;
|
|
10571
|
+
const productRe = /^[A-Za-z0-9_.\-]{1,32}\/[A-Za-z0-9_.+\-]{1,32}$/;
|
|
10572
|
+
const commentCharRe = /^[\x20-\x27\x2A-\x7E]{1,64}$/;
|
|
10573
|
+
const rawTokens = trimmed.split(/\s+/);
|
|
10574
|
+
const tokens = [];
|
|
10575
|
+
for (let i = 0;i < rawTokens.length; i += 1) {
|
|
10576
|
+
const tok = rawTokens[i];
|
|
10577
|
+
if (tok.startsWith("(") && !tok.endsWith(")")) {
|
|
10578
|
+
let acc = tok;
|
|
10579
|
+
while (i + 1 < rawTokens.length && !acc.endsWith(")")) {
|
|
10580
|
+
i += 1;
|
|
10581
|
+
acc += " " + rawTokens[i];
|
|
10582
|
+
}
|
|
10583
|
+
tokens.push(acc);
|
|
10584
|
+
} else {
|
|
10585
|
+
tokens.push(tok);
|
|
10586
|
+
}
|
|
10587
|
+
}
|
|
10588
|
+
const accepted = [];
|
|
10589
|
+
let pendingProduct = null;
|
|
10590
|
+
for (const tok of tokens) {
|
|
10591
|
+
if (tok.startsWith("(") && tok.endsWith(")")) {
|
|
10592
|
+
const inner = tok.slice(1, -1);
|
|
10593
|
+
if (pendingProduct && commentCharRe.test(inner)) {
|
|
10594
|
+
accepted.push(`${pendingProduct} (${inner})`);
|
|
10595
|
+
pendingProduct = null;
|
|
10596
|
+
} else {
|
|
10597
|
+
if (pendingProduct) {
|
|
10598
|
+
accepted.push(pendingProduct);
|
|
10599
|
+
pendingProduct = null;
|
|
10600
|
+
}
|
|
10601
|
+
}
|
|
10602
|
+
continue;
|
|
10603
|
+
}
|
|
10604
|
+
if (pendingProduct) {
|
|
10605
|
+
accepted.push(pendingProduct);
|
|
10606
|
+
pendingProduct = null;
|
|
10607
|
+
}
|
|
10608
|
+
if (productRe.test(tok)) {
|
|
10609
|
+
pendingProduct = tok;
|
|
10610
|
+
}
|
|
10611
|
+
}
|
|
10612
|
+
if (pendingProduct)
|
|
10613
|
+
accepted.push(pendingProduct);
|
|
10614
|
+
if (accepted.length === 0)
|
|
10615
|
+
return DEFAULT_BOT_AGENT;
|
|
10616
|
+
const joined = accepted.join(" ");
|
|
10617
|
+
if (Buffer.byteLength(joined, "utf-8") <= BOT_AGENT_MAX_LEN)
|
|
10618
|
+
return joined;
|
|
10619
|
+
const truncated = [];
|
|
10620
|
+
let len = 0;
|
|
10621
|
+
for (const t of accepted) {
|
|
10622
|
+
const add = (truncated.length === 0 ? 0 : 1) + Buffer.byteLength(t, "utf-8");
|
|
10623
|
+
if (len + add > BOT_AGENT_MAX_LEN)
|
|
10624
|
+
break;
|
|
10625
|
+
truncated.push(t);
|
|
10626
|
+
len += add;
|
|
10627
|
+
}
|
|
10628
|
+
return truncated.length > 0 ? truncated.join(" ") : DEFAULT_BOT_AGENT;
|
|
10629
|
+
}
|
|
10456
10630
|
function buildBaseInfo() {
|
|
10457
|
-
return {
|
|
10631
|
+
return {
|
|
10632
|
+
channel_version: CHANNEL_VERSION,
|
|
10633
|
+
bot_agent: sanitizeBotAgent(loadConfigBotAgent())
|
|
10634
|
+
};
|
|
10458
10635
|
}
|
|
10459
10636
|
function ensureTrailingSlash(url) {
|
|
10460
10637
|
return url.endsWith("/") ? url : `${url}/`;
|
|
@@ -10465,6 +10642,9 @@ function randomWechatUin() {
|
|
|
10465
10642
|
}
|
|
10466
10643
|
function buildCommonHeaders() {
|
|
10467
10644
|
const headers = {};
|
|
10645
|
+
if (ILINK_APP_ID)
|
|
10646
|
+
headers["iLink-App-Id"] = ILINK_APP_ID;
|
|
10647
|
+
headers["iLink-App-ClientVersion"] = String(ILINK_APP_CLIENT_VERSION);
|
|
10468
10648
|
const routeTag = loadConfigRouteTag();
|
|
10469
10649
|
if (routeTag) {
|
|
10470
10650
|
headers.SKRouteTag = routeTag;
|
|
@@ -10510,6 +10690,35 @@ async function apiGetFetch(params) {
|
|
|
10510
10690
|
throw err;
|
|
10511
10691
|
}
|
|
10512
10692
|
}
|
|
10693
|
+
async function apiPostFetch(params) {
|
|
10694
|
+
const base = ensureTrailingSlash(params.baseUrl);
|
|
10695
|
+
const url = new URL(params.endpoint, base);
|
|
10696
|
+
const hdrs = buildCommonHeaders();
|
|
10697
|
+
hdrs["Content-Type"] = "application/json";
|
|
10698
|
+
logger.debug(`POST ${redactUrl(url.toString())} body=${redactBody(params.body)}`);
|
|
10699
|
+
const controller = new AbortController;
|
|
10700
|
+
const t = params.timeoutMs !== undefined ? setTimeout(() => controller.abort(), params.timeoutMs) : undefined;
|
|
10701
|
+
const onAbort = () => controller.abort();
|
|
10702
|
+
params.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
10703
|
+
try {
|
|
10704
|
+
const res = await fetch(url.toString(), {
|
|
10705
|
+
method: "POST",
|
|
10706
|
+
headers: hdrs,
|
|
10707
|
+
body: params.body,
|
|
10708
|
+
signal: controller.signal
|
|
10709
|
+
});
|
|
10710
|
+
const rawText = await res.text();
|
|
10711
|
+
logger.debug(`${params.label} status=${res.status} raw=${redactBody(rawText)}`);
|
|
10712
|
+
if (!res.ok) {
|
|
10713
|
+
throw new Error(`${params.label} ${res.status}: ${rawText}`);
|
|
10714
|
+
}
|
|
10715
|
+
return rawText;
|
|
10716
|
+
} finally {
|
|
10717
|
+
if (t !== undefined)
|
|
10718
|
+
clearTimeout(t);
|
|
10719
|
+
params.abortSignal?.removeEventListener("abort", onAbort);
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10513
10722
|
async function apiFetch(params) {
|
|
10514
10723
|
const base = ensureTrailingSlash(params.baseUrl);
|
|
10515
10724
|
const url = new URL(params.endpoint, base);
|
|
@@ -10668,7 +10877,7 @@ async function sendTyping(params) {
|
|
|
10668
10877
|
label: "sendTyping"
|
|
10669
10878
|
});
|
|
10670
10879
|
}
|
|
10671
|
-
var CHANNEL_VERSION, DEFAULT_LONG_POLL_TIMEOUT_MS = 35000, DEFAULT_API_TIMEOUT_MS = 15000, DEFAULT_CONFIG_TIMEOUT_MS = 1e4;
|
|
10880
|
+
var CHANNEL_VERSION, ILINK_APP_CLIENT_VERSION, ILINK_APP_ID, DEFAULT_BOT_AGENT = "weacpx", BOT_AGENT_MAX_LEN = 256, DEFAULT_LONG_POLL_TIMEOUT_MS = 35000, DEFAULT_API_TIMEOUT_MS = 15000, DEFAULT_CONFIG_TIMEOUT_MS = 1e4;
|
|
10672
10881
|
var init_api = __esm(() => {
|
|
10673
10882
|
init_version();
|
|
10674
10883
|
init_accounts();
|
|
@@ -10676,6 +10885,8 @@ var init_api = __esm(() => {
|
|
|
10676
10885
|
init_redact();
|
|
10677
10886
|
init_send_errors();
|
|
10678
10887
|
CHANNEL_VERSION = readVersion();
|
|
10888
|
+
ILINK_APP_CLIENT_VERSION = buildClientVersion(CHANNEL_VERSION);
|
|
10889
|
+
ILINK_APP_ID = (process.env.WEACPX_ILINK_APP_ID ?? "").trim();
|
|
10679
10890
|
});
|
|
10680
10891
|
|
|
10681
10892
|
// node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
|
|
@@ -11707,22 +11918,47 @@ function purgeExpiredLogins() {
|
|
|
11707
11918
|
}
|
|
11708
11919
|
}
|
|
11709
11920
|
}
|
|
11921
|
+
function getLocalBotTokenList() {
|
|
11922
|
+
const accountIds = listIndexedWeixinAccountIds();
|
|
11923
|
+
const tokens = [];
|
|
11924
|
+
for (let i = accountIds.length - 1;i >= 0 && tokens.length < 10; i--) {
|
|
11925
|
+
const accountId = accountIds[i];
|
|
11926
|
+
if (!accountId)
|
|
11927
|
+
continue;
|
|
11928
|
+
const data = loadWeixinAccount(accountId);
|
|
11929
|
+
const token = data?.token?.trim();
|
|
11930
|
+
if (token) {
|
|
11931
|
+
tokens.push(token);
|
|
11932
|
+
}
|
|
11933
|
+
}
|
|
11934
|
+
return tokens;
|
|
11935
|
+
}
|
|
11710
11936
|
async function fetchQRCode(apiBaseUrl, botType) {
|
|
11711
11937
|
logger.info(`Fetching QR code from: ${apiBaseUrl} bot_type=${botType}`);
|
|
11712
|
-
const
|
|
11938
|
+
const localTokenList = getLocalBotTokenList();
|
|
11939
|
+
logger.info(`fetchQRCode: local_token_list count=${localTokenList.length}`);
|
|
11940
|
+
const rawText = await apiPostFetch({
|
|
11713
11941
|
baseUrl: apiBaseUrl,
|
|
11714
11942
|
endpoint: `ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(botType)}`,
|
|
11715
|
-
|
|
11943
|
+
body: JSON.stringify({ local_token_list: localTokenList }),
|
|
11716
11944
|
label: "fetchQRCode"
|
|
11717
11945
|
});
|
|
11718
11946
|
return JSON.parse(rawText);
|
|
11719
11947
|
}
|
|
11720
|
-
|
|
11721
|
-
|
|
11948
|
+
function buildPollQRStatusEndpoint(qrcode, verifyCode) {
|
|
11949
|
+
let endpoint = `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
|
|
11950
|
+
if (verifyCode) {
|
|
11951
|
+
endpoint += `&verify_code=${encodeURIComponent(verifyCode)}`;
|
|
11952
|
+
}
|
|
11953
|
+
return endpoint;
|
|
11954
|
+
}
|
|
11955
|
+
async function pollQRStatus(apiBaseUrl, qrcode, verifyCode) {
|
|
11956
|
+
logger.debug(`Long-poll QR status from: ${apiBaseUrl} qrcode=*** hasVerifyCode=${Boolean(verifyCode)}`);
|
|
11957
|
+
const endpoint = buildPollQRStatusEndpoint(qrcode, verifyCode);
|
|
11722
11958
|
try {
|
|
11723
11959
|
const rawText = await apiGetFetch({
|
|
11724
11960
|
baseUrl: apiBaseUrl,
|
|
11725
|
-
endpoint
|
|
11961
|
+
endpoint,
|
|
11726
11962
|
timeoutMs: QR_LONG_POLL_TIMEOUT_MS,
|
|
11727
11963
|
label: "pollQRStatus"
|
|
11728
11964
|
});
|
|
@@ -11737,6 +11973,28 @@ async function pollQRStatus(apiBaseUrl, qrcode) {
|
|
|
11737
11973
|
return { status: "wait" };
|
|
11738
11974
|
}
|
|
11739
11975
|
}
|
|
11976
|
+
async function readVerifyCodeFromStdin(prompt) {
|
|
11977
|
+
if (!process.stdin.isTTY) {
|
|
11978
|
+
throw new Error("verify code requested but stdin is not a TTY (running in daemon mode?)");
|
|
11979
|
+
}
|
|
11980
|
+
process.stdout.write(prompt);
|
|
11981
|
+
return new Promise((resolve) => {
|
|
11982
|
+
let input = "";
|
|
11983
|
+
const onData = (chunk) => {
|
|
11984
|
+
const str = chunk.toString();
|
|
11985
|
+
input += str;
|
|
11986
|
+
if (input.includes(`
|
|
11987
|
+
`)) {
|
|
11988
|
+
process.stdin.removeListener("data", onData);
|
|
11989
|
+
process.stdin.pause();
|
|
11990
|
+
resolve(input.trim());
|
|
11991
|
+
}
|
|
11992
|
+
};
|
|
11993
|
+
process.stdin.resume();
|
|
11994
|
+
process.stdin.setEncoding("utf-8");
|
|
11995
|
+
process.stdin.on("data", onData);
|
|
11996
|
+
});
|
|
11997
|
+
}
|
|
11740
11998
|
async function startWeixinLoginWithQr(opts) {
|
|
11741
11999
|
const sessionKey = opts.accountId || randomUUID2();
|
|
11742
12000
|
purgeExpiredLogins();
|
|
@@ -11775,6 +12033,36 @@ async function startWeixinLoginWithQr(opts) {
|
|
|
11775
12033
|
};
|
|
11776
12034
|
}
|
|
11777
12035
|
}
|
|
12036
|
+
async function refreshQRCode(activeLogin, botType, qrRefreshCount, onScannedReset) {
|
|
12037
|
+
try {
|
|
12038
|
+
const qrResponse = await fetchQRCode(FIXED_BASE_URL, botType);
|
|
12039
|
+
activeLogin.qrcode = qrResponse.qrcode;
|
|
12040
|
+
activeLogin.qrcodeUrl = qrResponse.qrcode_img_content;
|
|
12041
|
+
activeLogin.startedAt = Date.now();
|
|
12042
|
+
onScannedReset();
|
|
12043
|
+
logger.info(`refreshQRCode: new QR code obtained (${qrRefreshCount}/${MAX_QR_REFRESH_COUNT}) qrcode=${redactToken(qrResponse.qrcode)}`);
|
|
12044
|
+
process.stdout.write(`\uD83D\uDD04 新二维码已生成,请重新扫描
|
|
12045
|
+
|
|
12046
|
+
`);
|
|
12047
|
+
try {
|
|
12048
|
+
const qrterm = await Promise.resolve().then(() => __toESM(require_main(), 1));
|
|
12049
|
+
qrterm.default.generate(qrResponse.qrcode_img_content, { small: true });
|
|
12050
|
+
process.stdout.write(`如果二维码未能成功展示,请用浏览器打开以下链接扫码:
|
|
12051
|
+
`);
|
|
12052
|
+
process.stdout.write(`${qrResponse.qrcode_img_content}
|
|
12053
|
+
`);
|
|
12054
|
+
} catch {
|
|
12055
|
+
process.stdout.write(`二维码未加载成功,请用浏览器打开以下链接扫码:
|
|
12056
|
+
`);
|
|
12057
|
+
process.stdout.write(`${qrResponse.qrcode_img_content}
|
|
12058
|
+
`);
|
|
12059
|
+
}
|
|
12060
|
+
return { success: true };
|
|
12061
|
+
} catch (refreshErr) {
|
|
12062
|
+
logger.error(`refreshQRCode: failed to refresh QR code: ${String(refreshErr)}`);
|
|
12063
|
+
return { success: false, message: `刷新二维码失败: ${String(refreshErr)}` };
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
11778
12066
|
async function waitForWeixinLogin(opts) {
|
|
11779
12067
|
let activeLogin = activeLogins.get(opts.sessionKey);
|
|
11780
12068
|
if (!activeLogin) {
|
|
@@ -11801,7 +12089,7 @@ async function waitForWeixinLogin(opts) {
|
|
|
11801
12089
|
while (Date.now() < deadline) {
|
|
11802
12090
|
try {
|
|
11803
12091
|
const currentBaseUrl = activeLogin.currentApiBaseUrl ?? FIXED_BASE_URL;
|
|
11804
|
-
const statusResponse = await pollQRStatus(currentBaseUrl, activeLogin.qrcode);
|
|
12092
|
+
const statusResponse = await pollQRStatus(currentBaseUrl, activeLogin.qrcode, activeLogin.pendingVerifyCode);
|
|
11805
12093
|
logger.debug(`pollQRStatus: status=${statusResponse.status} hasBotToken=${Boolean(statusResponse.bot_token)} hasBotId=${Boolean(statusResponse.ilink_bot_id)}`);
|
|
11806
12094
|
activeLogin.status = statusResponse.status;
|
|
11807
12095
|
switch (statusResponse.status) {
|
|
@@ -11811,6 +12099,10 @@ async function waitForWeixinLogin(opts) {
|
|
|
11811
12099
|
}
|
|
11812
12100
|
break;
|
|
11813
12101
|
case "scaned":
|
|
12102
|
+
if (activeLogin.pendingVerifyCode) {
|
|
12103
|
+
logger.info("verify code accepted, resuming polling");
|
|
12104
|
+
activeLogin.pendingVerifyCode = undefined;
|
|
12105
|
+
}
|
|
11814
12106
|
if (!scannedPrinted) {
|
|
11815
12107
|
process.stdout.write(`
|
|
11816
12108
|
\uD83D\uDC40 已扫码,在微信继续操作...
|
|
@@ -11819,6 +12111,7 @@ async function waitForWeixinLogin(opts) {
|
|
|
11819
12111
|
}
|
|
11820
12112
|
break;
|
|
11821
12113
|
case "expired": {
|
|
12114
|
+
activeLogin.pendingVerifyCode = undefined;
|
|
11822
12115
|
qrRefreshCount++;
|
|
11823
12116
|
if (qrRefreshCount > MAX_QR_REFRESH_COUNT) {
|
|
11824
12117
|
logger.warn(`waitForWeixinLogin: QR expired ${MAX_QR_REFRESH_COUNT} times, giving up sessionKey=${opts.sessionKey}`);
|
|
@@ -11832,36 +12125,14 @@ async function waitForWeixinLogin(opts) {
|
|
|
11832
12125
|
⏳ 二维码已过期,正在刷新...(${qrRefreshCount}/${MAX_QR_REFRESH_COUNT})
|
|
11833
12126
|
`);
|
|
11834
12127
|
logger.info(`waitForWeixinLogin: QR expired, refreshing (${qrRefreshCount}/${MAX_QR_REFRESH_COUNT})`);
|
|
11835
|
-
|
|
11836
|
-
const botType = opts.botType || DEFAULT_ILINK_BOT_TYPE;
|
|
11837
|
-
const qrResponse = await fetchQRCode(FIXED_BASE_URL, botType);
|
|
11838
|
-
activeLogin.qrcode = qrResponse.qrcode;
|
|
11839
|
-
activeLogin.qrcodeUrl = qrResponse.qrcode_img_content;
|
|
11840
|
-
activeLogin.startedAt = Date.now();
|
|
12128
|
+
const expiredRefreshResult = await refreshQRCode(activeLogin, opts.botType || DEFAULT_ILINK_BOT_TYPE, qrRefreshCount, () => {
|
|
11841
12129
|
scannedPrinted = false;
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
`);
|
|
11846
|
-
try {
|
|
11847
|
-
const qrterm = await Promise.resolve().then(() => __toESM(require_main(), 1));
|
|
11848
|
-
qrterm.default.generate(qrResponse.qrcode_img_content, { small: true });
|
|
11849
|
-
process.stdout.write(`如果二维码未能成功展示,请用浏览器打开以下链接扫码:
|
|
11850
|
-
`);
|
|
11851
|
-
process.stdout.write(`${qrResponse.qrcode_img_content}
|
|
11852
|
-
`);
|
|
11853
|
-
} catch {
|
|
11854
|
-
process.stdout.write(`二维码未加载成功,请用浏览器打开以下链接扫码:
|
|
11855
|
-
`);
|
|
11856
|
-
process.stdout.write(`${qrResponse.qrcode_img_content}
|
|
11857
|
-
`);
|
|
11858
|
-
}
|
|
11859
|
-
} catch (refreshErr) {
|
|
11860
|
-
logger.error(`waitForWeixinLogin: failed to refresh QR code: ${String(refreshErr)}`);
|
|
12130
|
+
});
|
|
12131
|
+
if (!expiredRefreshResult.success) {
|
|
11861
12132
|
activeLogins.delete(opts.sessionKey);
|
|
11862
12133
|
return {
|
|
11863
12134
|
connected: false,
|
|
11864
|
-
message:
|
|
12135
|
+
message: expiredRefreshResult.message
|
|
11865
12136
|
};
|
|
11866
12137
|
}
|
|
11867
12138
|
break;
|
|
@@ -11877,6 +12148,49 @@ async function waitForWeixinLogin(opts) {
|
|
|
11877
12148
|
}
|
|
11878
12149
|
break;
|
|
11879
12150
|
}
|
|
12151
|
+
case "need_verifycode": {
|
|
12152
|
+
const verifyPrompt = activeLogin.pendingVerifyCode ? "❌ 你输入的数字不匹配,请重新输入:" : "输入手机微信显示的数字,以继续连接:";
|
|
12153
|
+
let code;
|
|
12154
|
+
try {
|
|
12155
|
+
code = await readVerifyCodeFromStdin(verifyPrompt);
|
|
12156
|
+
} catch (err) {
|
|
12157
|
+
logger.error(`waitForWeixinLogin: cannot read verify code (no TTY): ${String(err)}`);
|
|
12158
|
+
activeLogins.delete(opts.sessionKey);
|
|
12159
|
+
return {
|
|
12160
|
+
connected: false,
|
|
12161
|
+
message: "需要输入配对码,但当前环境没有交互式终端。请在前台运行 `weacpx login` 完成登录。"
|
|
12162
|
+
};
|
|
12163
|
+
}
|
|
12164
|
+
activeLogin.pendingVerifyCode = code;
|
|
12165
|
+
continue;
|
|
12166
|
+
}
|
|
12167
|
+
case "verify_code_blocked": {
|
|
12168
|
+
logger.warn(`waitForWeixinLogin: verify code blocked, qrRefreshCount=${qrRefreshCount} sessionKey=${opts.sessionKey}`);
|
|
12169
|
+
process.stdout.write(`
|
|
12170
|
+
⛔ 多次输入错误,请稍后再试。
|
|
12171
|
+
`);
|
|
12172
|
+
activeLogin.pendingVerifyCode = undefined;
|
|
12173
|
+
qrRefreshCount++;
|
|
12174
|
+
if (qrRefreshCount > MAX_QR_REFRESH_COUNT) {
|
|
12175
|
+
logger.warn(`waitForWeixinLogin: verify_code_blocked and QR refresh limit reached, giving up sessionKey=${opts.sessionKey}`);
|
|
12176
|
+
activeLogins.delete(opts.sessionKey);
|
|
12177
|
+
return {
|
|
12178
|
+
connected: false,
|
|
12179
|
+
message: "多次输入错误,连接流程已停止。请稍后再试。"
|
|
12180
|
+
};
|
|
12181
|
+
}
|
|
12182
|
+
const blockedRefreshResult = await refreshQRCode(activeLogin, opts.botType || DEFAULT_ILINK_BOT_TYPE, qrRefreshCount, () => {
|
|
12183
|
+
scannedPrinted = false;
|
|
12184
|
+
});
|
|
12185
|
+
if (!blockedRefreshResult.success) {
|
|
12186
|
+
activeLogins.delete(opts.sessionKey);
|
|
12187
|
+
return {
|
|
12188
|
+
connected: false,
|
|
12189
|
+
message: blockedRefreshResult.message
|
|
12190
|
+
};
|
|
12191
|
+
}
|
|
12192
|
+
break;
|
|
12193
|
+
}
|
|
11880
12194
|
case "confirmed": {
|
|
11881
12195
|
if (!statusResponse.ilink_bot_id) {
|
|
11882
12196
|
activeLogins.delete(opts.sessionKey);
|
|
@@ -11916,15 +12230,201 @@ async function waitForWeixinLogin(opts) {
|
|
|
11916
12230
|
message: "登录超时,请重试。"
|
|
11917
12231
|
};
|
|
11918
12232
|
}
|
|
11919
|
-
var ACTIVE_LOGIN_TTL_MS,
|
|
12233
|
+
var ACTIVE_LOGIN_TTL_MS, QR_LONG_POLL_TIMEOUT_MS = 35000, DEFAULT_ILINK_BOT_TYPE = "3", FIXED_BASE_URL = "https://ilinkai.weixin.qq.com", activeLogins, MAX_QR_REFRESH_COUNT = 3;
|
|
11920
12234
|
var init_login_qr = __esm(() => {
|
|
11921
12235
|
init_api();
|
|
12236
|
+
init_accounts();
|
|
11922
12237
|
init_logger();
|
|
11923
12238
|
init_redact();
|
|
11924
12239
|
ACTIVE_LOGIN_TTL_MS = 5 * 60000;
|
|
11925
12240
|
activeLogins = new Map;
|
|
11926
12241
|
});
|
|
11927
12242
|
|
|
12243
|
+
// src/weixin/util/random.ts
|
|
12244
|
+
import crypto2 from "node:crypto";
|
|
12245
|
+
function generateId(prefix) {
|
|
12246
|
+
return `${prefix}:${Date.now()}-${crypto2.randomBytes(4).toString("hex")}`;
|
|
12247
|
+
}
|
|
12248
|
+
var init_random = () => {};
|
|
12249
|
+
|
|
12250
|
+
// src/weixin/api/types.ts
|
|
12251
|
+
var UploadMediaType, MessageType, MessageItemType, MessageState, TypingStatus;
|
|
12252
|
+
var init_types2 = __esm(() => {
|
|
12253
|
+
UploadMediaType = {
|
|
12254
|
+
IMAGE: 1,
|
|
12255
|
+
VIDEO: 2,
|
|
12256
|
+
FILE: 3,
|
|
12257
|
+
VOICE: 4
|
|
12258
|
+
};
|
|
12259
|
+
MessageType = {
|
|
12260
|
+
NONE: 0,
|
|
12261
|
+
USER: 1,
|
|
12262
|
+
BOT: 2
|
|
12263
|
+
};
|
|
12264
|
+
MessageItemType = {
|
|
12265
|
+
NONE: 0,
|
|
12266
|
+
TEXT: 1,
|
|
12267
|
+
IMAGE: 2,
|
|
12268
|
+
VOICE: 3,
|
|
12269
|
+
FILE: 4,
|
|
12270
|
+
VIDEO: 5
|
|
12271
|
+
};
|
|
12272
|
+
MessageState = {
|
|
12273
|
+
NEW: 0,
|
|
12274
|
+
GENERATING: 1,
|
|
12275
|
+
FINISH: 2
|
|
12276
|
+
};
|
|
12277
|
+
TypingStatus = {
|
|
12278
|
+
TYPING: 1,
|
|
12279
|
+
CANCEL: 2
|
|
12280
|
+
};
|
|
12281
|
+
});
|
|
12282
|
+
|
|
12283
|
+
// src/weixin/messaging/inbound.ts
|
|
12284
|
+
import fs5 from "node:fs";
|
|
12285
|
+
import path6 from "node:path";
|
|
12286
|
+
function contextTokenKey(accountId, userId) {
|
|
12287
|
+
return `${accountId}:${userId}`;
|
|
12288
|
+
}
|
|
12289
|
+
function resolveContextTokenFilePath(accountId) {
|
|
12290
|
+
return path6.join(resolveStateDir(), "openclaw-weixin", "accounts", `${accountId}.context-tokens.json`);
|
|
12291
|
+
}
|
|
12292
|
+
function persistContextTokens(accountId) {
|
|
12293
|
+
const prefix = `${accountId}:`;
|
|
12294
|
+
const tokens = {};
|
|
12295
|
+
for (const [k, v] of contextTokenStore) {
|
|
12296
|
+
if (k.startsWith(prefix))
|
|
12297
|
+
tokens[k.slice(prefix.length)] = v;
|
|
12298
|
+
}
|
|
12299
|
+
const filePath = resolveContextTokenFilePath(accountId);
|
|
12300
|
+
try {
|
|
12301
|
+
fs5.mkdirSync(path6.dirname(filePath), { recursive: true });
|
|
12302
|
+
fs5.writeFileSync(filePath, JSON.stringify(tokens), "utf-8");
|
|
12303
|
+
} catch (err) {
|
|
12304
|
+
logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);
|
|
12305
|
+
}
|
|
12306
|
+
}
|
|
12307
|
+
function restoreContextTokens(accountId) {
|
|
12308
|
+
const filePath = resolveContextTokenFilePath(accountId);
|
|
12309
|
+
try {
|
|
12310
|
+
if (!fs5.existsSync(filePath))
|
|
12311
|
+
return;
|
|
12312
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
12313
|
+
const tokens = JSON.parse(raw);
|
|
12314
|
+
let count = 0;
|
|
12315
|
+
for (const [userId, token] of Object.entries(tokens)) {
|
|
12316
|
+
if (typeof token === "string" && token) {
|
|
12317
|
+
contextTokenStore.set(contextTokenKey(accountId, userId), token);
|
|
12318
|
+
count++;
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
logger.info(`restoreContextTokens: restored ${count} tokens for account=${accountId}`);
|
|
12322
|
+
} catch (err) {
|
|
12323
|
+
logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
12326
|
+
function clearContextTokensForAccount(accountId) {
|
|
12327
|
+
const prefix = `${accountId}:`;
|
|
12328
|
+
for (const k of [...contextTokenStore.keys()]) {
|
|
12329
|
+
if (k.startsWith(prefix))
|
|
12330
|
+
contextTokenStore.delete(k);
|
|
12331
|
+
}
|
|
12332
|
+
const filePath = resolveContextTokenFilePath(accountId);
|
|
12333
|
+
try {
|
|
12334
|
+
if (fs5.existsSync(filePath))
|
|
12335
|
+
fs5.unlinkSync(filePath);
|
|
12336
|
+
} catch (err) {
|
|
12337
|
+
logger.warn(`clearContextTokensForAccount: failed to remove ${filePath}: ${String(err)}`);
|
|
12338
|
+
}
|
|
12339
|
+
logger.info(`clearContextTokensForAccount: cleared tokens for account=${accountId}`);
|
|
12340
|
+
}
|
|
12341
|
+
function setContextToken(accountId, userId, token) {
|
|
12342
|
+
const k = contextTokenKey(accountId, userId);
|
|
12343
|
+
logger.debug(`setContextToken: key=${k}`);
|
|
12344
|
+
contextTokenStore.set(k, token);
|
|
12345
|
+
persistContextTokens(accountId);
|
|
12346
|
+
}
|
|
12347
|
+
function getContextToken(accountId, userId) {
|
|
12348
|
+
const k = contextTokenKey(accountId, normalizeWeixinUserIdFromChatKey(userId));
|
|
12349
|
+
const val = contextTokenStore.get(k);
|
|
12350
|
+
logger.debug(`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`);
|
|
12351
|
+
return val;
|
|
12352
|
+
}
|
|
12353
|
+
function normalizeWeixinUserIdFromChatKey(chatKey) {
|
|
12354
|
+
const parts = chatKey.split(":");
|
|
12355
|
+
if (parts[0] === "weixin" && parts[2]) {
|
|
12356
|
+
return parts.slice(2).join(":");
|
|
12357
|
+
}
|
|
12358
|
+
return chatKey;
|
|
12359
|
+
}
|
|
12360
|
+
function isMediaItem(item) {
|
|
12361
|
+
return item.type === MessageItemType.IMAGE || item.type === MessageItemType.VIDEO || item.type === MessageItemType.FILE || item.type === MessageItemType.VOICE;
|
|
12362
|
+
}
|
|
12363
|
+
function bodyFromItemList(itemList) {
|
|
12364
|
+
if (!itemList?.length)
|
|
12365
|
+
return "";
|
|
12366
|
+
for (const item of itemList) {
|
|
12367
|
+
if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {
|
|
12368
|
+
const text = String(item.text_item.text);
|
|
12369
|
+
const ref = item.ref_msg;
|
|
12370
|
+
if (!ref)
|
|
12371
|
+
return text;
|
|
12372
|
+
if (ref.message_item && isMediaItem(ref.message_item))
|
|
12373
|
+
return text;
|
|
12374
|
+
const parts = [];
|
|
12375
|
+
if (ref.title)
|
|
12376
|
+
parts.push(ref.title);
|
|
12377
|
+
if (ref.message_item) {
|
|
12378
|
+
const refBody = bodyFromItemList([ref.message_item]);
|
|
12379
|
+
if (refBody)
|
|
12380
|
+
parts.push(refBody);
|
|
12381
|
+
}
|
|
12382
|
+
if (!parts.length)
|
|
12383
|
+
return text;
|
|
12384
|
+
return `[引用: ${parts.join(" | ")}]
|
|
12385
|
+
${text}`;
|
|
12386
|
+
}
|
|
12387
|
+
if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|
|
12388
|
+
return item.voice_item.text;
|
|
12389
|
+
}
|
|
12390
|
+
}
|
|
12391
|
+
return "";
|
|
12392
|
+
}
|
|
12393
|
+
function extractWeixinMediaDescriptors(itemList) {
|
|
12394
|
+
const out = [];
|
|
12395
|
+
for (const item of itemList ?? []) {
|
|
12396
|
+
const descriptor = descriptorFromItem(item);
|
|
12397
|
+
if (descriptor)
|
|
12398
|
+
out.push(descriptor);
|
|
12399
|
+
const ref = item.type === MessageItemType.TEXT ? item.ref_msg?.message_item : undefined;
|
|
12400
|
+
const refDescriptor = descriptorFromItem(ref);
|
|
12401
|
+
if (refDescriptor)
|
|
12402
|
+
out.push(refDescriptor);
|
|
12403
|
+
}
|
|
12404
|
+
return out;
|
|
12405
|
+
}
|
|
12406
|
+
function descriptorFromItem(item) {
|
|
12407
|
+
if (!item)
|
|
12408
|
+
return;
|
|
12409
|
+
if (item.type === MessageItemType.IMAGE)
|
|
12410
|
+
return { item, kind: "image" };
|
|
12411
|
+
if (item.type === MessageItemType.VIDEO)
|
|
12412
|
+
return { item, kind: "video" };
|
|
12413
|
+
if (item.type === MessageItemType.FILE)
|
|
12414
|
+
return { item, kind: "file", fileName: item.file_item?.file_name };
|
|
12415
|
+
if (item.type === MessageItemType.VOICE)
|
|
12416
|
+
return { item, kind: "audio" };
|
|
12417
|
+
return;
|
|
12418
|
+
}
|
|
12419
|
+
var contextTokenStore;
|
|
12420
|
+
var init_inbound = __esm(() => {
|
|
12421
|
+
init_logger();
|
|
12422
|
+
init_random();
|
|
12423
|
+
init_types2();
|
|
12424
|
+
init_state_dir();
|
|
12425
|
+
contextTokenStore = new Map;
|
|
12426
|
+
});
|
|
12427
|
+
|
|
11928
12428
|
// src/weixin/api/config-cache.ts
|
|
11929
12429
|
class WeixinConfigManager {
|
|
11930
12430
|
apiOpts;
|
|
@@ -11992,16 +12492,8 @@ function pauseSession(accountId) {
|
|
|
11992
12492
|
pauseUntilMap.set(accountId, until);
|
|
11993
12493
|
logger.info(`session-guard: paused accountId=${accountId} until=${new Date(until).toISOString()} (${SESSION_PAUSE_DURATION_MS / 1000}s)`);
|
|
11994
12494
|
}
|
|
11995
|
-
function
|
|
11996
|
-
|
|
11997
|
-
if (until === undefined)
|
|
11998
|
-
return 0;
|
|
11999
|
-
const remaining = until - Date.now();
|
|
12000
|
-
if (remaining <= 0) {
|
|
12001
|
-
pauseUntilMap.delete(accountId);
|
|
12002
|
-
return 0;
|
|
12003
|
-
}
|
|
12004
|
-
return remaining;
|
|
12495
|
+
function resetSessionPause(accountId) {
|
|
12496
|
+
pauseUntilMap.delete(accountId);
|
|
12005
12497
|
}
|
|
12006
12498
|
var SESSION_PAUSE_DURATION_MS, SESSION_EXPIRED_ERRCODE = -14, pauseUntilMap;
|
|
12007
12499
|
var init_session_guard = __esm(() => {
|
|
@@ -12051,42 +12543,9 @@ function createConversationExecutor() {
|
|
|
12051
12543
|
};
|
|
12052
12544
|
}
|
|
12053
12545
|
|
|
12054
|
-
// src/weixin/api/types.ts
|
|
12055
|
-
var UploadMediaType, MessageType, MessageItemType, MessageState, TypingStatus;
|
|
12056
|
-
var init_types2 = __esm(() => {
|
|
12057
|
-
UploadMediaType = {
|
|
12058
|
-
IMAGE: 1,
|
|
12059
|
-
VIDEO: 2,
|
|
12060
|
-
FILE: 3,
|
|
12061
|
-
VOICE: 4
|
|
12062
|
-
};
|
|
12063
|
-
MessageType = {
|
|
12064
|
-
NONE: 0,
|
|
12065
|
-
USER: 1,
|
|
12066
|
-
BOT: 2
|
|
12067
|
-
};
|
|
12068
|
-
MessageItemType = {
|
|
12069
|
-
NONE: 0,
|
|
12070
|
-
TEXT: 1,
|
|
12071
|
-
IMAGE: 2,
|
|
12072
|
-
VOICE: 3,
|
|
12073
|
-
FILE: 4,
|
|
12074
|
-
VIDEO: 5
|
|
12075
|
-
};
|
|
12076
|
-
MessageState = {
|
|
12077
|
-
NEW: 0,
|
|
12078
|
-
GENERATING: 1,
|
|
12079
|
-
FINISH: 2
|
|
12080
|
-
};
|
|
12081
|
-
TypingStatus = {
|
|
12082
|
-
TYPING: 1,
|
|
12083
|
-
CANCEL: 2
|
|
12084
|
-
};
|
|
12085
|
-
});
|
|
12086
|
-
|
|
12087
12546
|
// src/channels/media-store.ts
|
|
12088
12547
|
import { access as access2, mkdir as mkdir7, readdir, rm as rm4, stat, writeFile as writeFile6 } from "node:fs/promises";
|
|
12089
|
-
import
|
|
12548
|
+
import path7 from "node:path";
|
|
12090
12549
|
|
|
12091
12550
|
class RuntimeMediaStore {
|
|
12092
12551
|
rootDir;
|
|
@@ -12102,10 +12561,10 @@ class RuntimeMediaStore {
|
|
|
12102
12561
|
const safeChatKey = safePathSegment(input.chatKey);
|
|
12103
12562
|
const safeMessageId = safePathSegment(input.messageId || "message");
|
|
12104
12563
|
const baseFileName = sanitizeMediaFileName(input.fileName ?? "attachment", input.mimeType);
|
|
12105
|
-
const dir =
|
|
12564
|
+
const dir = path7.join(this.rootDir, input.channelId, safeChatKey, safeMessageId);
|
|
12106
12565
|
await mkdir7(dir, { recursive: true });
|
|
12107
|
-
const resolvedRoot =
|
|
12108
|
-
const resolvedFile =
|
|
12566
|
+
const resolvedRoot = path7.resolve(this.rootDir);
|
|
12567
|
+
const resolvedFile = path7.resolve(path7.join(dir, await uniqueFileName(dir, baseFileName)));
|
|
12109
12568
|
if (!isPathInside(resolvedFile, resolvedRoot)) {
|
|
12110
12569
|
throw new Error("media path escapes runtime media root");
|
|
12111
12570
|
}
|
|
@@ -12114,7 +12573,7 @@ class RuntimeMediaStore {
|
|
|
12114
12573
|
kind: input.kind,
|
|
12115
12574
|
filePath: resolvedFile,
|
|
12116
12575
|
mimeType: input.mimeType,
|
|
12117
|
-
fileName:
|
|
12576
|
+
fileName: path7.basename(resolvedFile),
|
|
12118
12577
|
sizeBytes: input.buffer.byteLength,
|
|
12119
12578
|
source: {
|
|
12120
12579
|
channelId: input.channelId,
|
|
@@ -12130,10 +12589,10 @@ class RuntimeMediaStore {
|
|
|
12130
12589
|
}
|
|
12131
12590
|
}
|
|
12132
12591
|
function sanitizeMediaFileName(fileName, mimeType) {
|
|
12133
|
-
const base =
|
|
12592
|
+
const base = path7.basename(fileName.trim() || "attachment");
|
|
12134
12593
|
const replaced = base.replace(/[\\/:*?"<>|\s]+/g, "-").replace(/^-+|-+$/g, "");
|
|
12135
12594
|
const safe = replaced || "attachment";
|
|
12136
|
-
const ext =
|
|
12595
|
+
const ext = path7.extname(safe);
|
|
12137
12596
|
if (ext)
|
|
12138
12597
|
return safe;
|
|
12139
12598
|
return `${safe}${extensionFromMime(mimeType)}`;
|
|
@@ -12143,13 +12602,13 @@ function safePathSegment(value) {
|
|
|
12143
12602
|
return safe || "unknown";
|
|
12144
12603
|
}
|
|
12145
12604
|
async function uniqueFileName(dir, baseName) {
|
|
12146
|
-
const ext =
|
|
12605
|
+
const ext = path7.extname(baseName);
|
|
12147
12606
|
const stem = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
12148
12607
|
let candidate = baseName;
|
|
12149
12608
|
let counter = 2;
|
|
12150
12609
|
while (true) {
|
|
12151
12610
|
try {
|
|
12152
|
-
await access2(
|
|
12611
|
+
await access2(path7.join(dir, candidate));
|
|
12153
12612
|
candidate = `${stem}-${counter}${ext}`;
|
|
12154
12613
|
counter += 1;
|
|
12155
12614
|
} catch {
|
|
@@ -12180,8 +12639,8 @@ function extensionFromMime(mimeType) {
|
|
|
12180
12639
|
return ".bin";
|
|
12181
12640
|
}
|
|
12182
12641
|
function isPathInside(candidate, root) {
|
|
12183
|
-
const relative =
|
|
12184
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
12642
|
+
const relative = path7.relative(root, candidate);
|
|
12643
|
+
return relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative);
|
|
12185
12644
|
}
|
|
12186
12645
|
async function cleanupDir(dir, cutoffMs) {
|
|
12187
12646
|
let entries;
|
|
@@ -12192,7 +12651,7 @@ async function cleanupDir(dir, cutoffMs) {
|
|
|
12192
12651
|
}
|
|
12193
12652
|
let empty = true;
|
|
12194
12653
|
for (const entry of entries) {
|
|
12195
|
-
const full =
|
|
12654
|
+
const full = path7.join(dir, entry.name);
|
|
12196
12655
|
if (entry.isDirectory()) {
|
|
12197
12656
|
const childEmpty = await cleanupDir(full, cutoffMs);
|
|
12198
12657
|
if (childEmpty) {
|
|
@@ -12221,18 +12680,18 @@ var init_media_store = __esm(() => {
|
|
|
12221
12680
|
});
|
|
12222
12681
|
|
|
12223
12682
|
// src/channels/outbound-media-safety.ts
|
|
12224
|
-
import
|
|
12225
|
-
import
|
|
12683
|
+
import fs6 from "node:fs/promises";
|
|
12684
|
+
import path8 from "node:path";
|
|
12226
12685
|
async function resolveSafeOutboundMediaPath(mediaPath, allowedRoots) {
|
|
12227
12686
|
if (mediaPath.startsWith("http://") || mediaPath.startsWith("https://")) {
|
|
12228
12687
|
return null;
|
|
12229
12688
|
}
|
|
12230
|
-
const candidate =
|
|
12689
|
+
const candidate = path8.isAbsolute(mediaPath) ? mediaPath : path8.resolve(mediaPath);
|
|
12231
12690
|
const realCandidate = await realpathOrNull(candidate);
|
|
12232
12691
|
if (!realCandidate) {
|
|
12233
12692
|
return null;
|
|
12234
12693
|
}
|
|
12235
|
-
const stat2 = await
|
|
12694
|
+
const stat2 = await fs6.stat(realCandidate).catch(() => null);
|
|
12236
12695
|
if (!stat2?.isFile()) {
|
|
12237
12696
|
return null;
|
|
12238
12697
|
}
|
|
@@ -12246,21 +12705,21 @@ async function resolveSafeOutboundMediaPath(mediaPath, allowedRoots) {
|
|
|
12246
12705
|
}
|
|
12247
12706
|
async function realpathOrNull(filePath) {
|
|
12248
12707
|
try {
|
|
12249
|
-
return await
|
|
12708
|
+
return await fs6.realpath(filePath);
|
|
12250
12709
|
} catch {
|
|
12251
12710
|
return null;
|
|
12252
12711
|
}
|
|
12253
12712
|
}
|
|
12254
12713
|
function isPathInside2(candidate, root) {
|
|
12255
|
-
const relative =
|
|
12256
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
12714
|
+
const relative = path8.relative(root, candidate);
|
|
12715
|
+
return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
|
|
12257
12716
|
}
|
|
12258
12717
|
var init_outbound_media_safety = () => {};
|
|
12259
12718
|
|
|
12260
12719
|
// src/weixin/media/mime.ts
|
|
12261
|
-
import
|
|
12720
|
+
import path9 from "node:path";
|
|
12262
12721
|
function getMimeFromFilename(filename) {
|
|
12263
|
-
const ext =
|
|
12722
|
+
const ext = path9.extname(filename).toLowerCase();
|
|
12264
12723
|
return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
|
|
12265
12724
|
}
|
|
12266
12725
|
function getExtensionFromMime(mimeType) {
|
|
@@ -12598,115 +13057,361 @@ function buildFinalHeadsUp(input) {
|
|
|
12598
13057
|
\uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`;
|
|
12599
13058
|
}
|
|
12600
13059
|
|
|
12601
|
-
// src/weixin/
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
return `${accountId}:${userId}`;
|
|
12611
|
-
}
|
|
12612
|
-
function setContextToken(accountId, userId, token) {
|
|
12613
|
-
const k = contextTokenKey(accountId, userId);
|
|
12614
|
-
logger.debug(`setContextToken: key=${k}`);
|
|
12615
|
-
contextTokenStore.set(k, token);
|
|
12616
|
-
}
|
|
12617
|
-
function getContextToken(accountId, userId) {
|
|
12618
|
-
const k = contextTokenKey(accountId, normalizeWeixinUserIdFromChatKey(userId));
|
|
12619
|
-
const val = contextTokenStore.get(k);
|
|
12620
|
-
logger.debug(`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`);
|
|
12621
|
-
return val;
|
|
12622
|
-
}
|
|
12623
|
-
function normalizeWeixinUserIdFromChatKey(chatKey) {
|
|
12624
|
-
const parts = chatKey.split(":");
|
|
12625
|
-
if (parts[0] === "weixin" && parts[2]) {
|
|
12626
|
-
return parts.slice(2).join(":");
|
|
13060
|
+
// src/weixin/messaging/markdown-filter.ts
|
|
13061
|
+
class StreamingMarkdownFilter {
|
|
13062
|
+
buf = "";
|
|
13063
|
+
fence = false;
|
|
13064
|
+
sol = true;
|
|
13065
|
+
inl = null;
|
|
13066
|
+
feed(delta) {
|
|
13067
|
+
this.buf += delta;
|
|
13068
|
+
return this.pump(false);
|
|
12627
13069
|
}
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
13070
|
+
flush() {
|
|
13071
|
+
return this.pump(true);
|
|
13072
|
+
}
|
|
13073
|
+
pump(eof) {
|
|
13074
|
+
let out = "";
|
|
13075
|
+
while (this.buf) {
|
|
13076
|
+
const sLen = this.buf.length;
|
|
13077
|
+
const sSol = this.sol;
|
|
13078
|
+
const sFence = this.fence;
|
|
13079
|
+
const sInl = this.inl;
|
|
13080
|
+
if (this.fence)
|
|
13081
|
+
out += this.pumpFence(eof);
|
|
13082
|
+
else if (this.inl)
|
|
13083
|
+
out += this.pumpInline(eof);
|
|
13084
|
+
else if (this.sol)
|
|
13085
|
+
out += this.pumpSOL(eof);
|
|
13086
|
+
else
|
|
13087
|
+
out += this.pumpBody(eof);
|
|
13088
|
+
if (this.buf.length === sLen && this.sol === sSol && this.fence === sFence && this.inl === sInl)
|
|
13089
|
+
break;
|
|
13090
|
+
}
|
|
13091
|
+
if (eof && this.inl) {
|
|
13092
|
+
const markers = { image: "![", bold3: "***", italic: "*", ubold3: "___", uitalic: "_" };
|
|
13093
|
+
out += (markers[this.inl.type] ?? "") + this.inl.acc;
|
|
13094
|
+
this.inl = null;
|
|
13095
|
+
}
|
|
13096
|
+
return out;
|
|
13097
|
+
}
|
|
13098
|
+
pumpFence(eof) {
|
|
13099
|
+
if (this.sol) {
|
|
13100
|
+
if (this.buf.length < 3 && !eof)
|
|
13101
|
+
return "";
|
|
13102
|
+
if (this.buf.startsWith("```")) {
|
|
13103
|
+
const nl2 = this.buf.indexOf(`
|
|
13104
|
+
`, 3);
|
|
13105
|
+
if (nl2 !== -1) {
|
|
13106
|
+
this.fence = false;
|
|
13107
|
+
const line = this.buf.slice(0, nl2 + 1);
|
|
13108
|
+
this.buf = this.buf.slice(nl2 + 1);
|
|
13109
|
+
this.sol = true;
|
|
13110
|
+
return line;
|
|
13111
|
+
}
|
|
13112
|
+
if (eof) {
|
|
13113
|
+
this.fence = false;
|
|
13114
|
+
const line = this.buf;
|
|
13115
|
+
this.buf = "";
|
|
13116
|
+
return line;
|
|
13117
|
+
}
|
|
13118
|
+
return "";
|
|
13119
|
+
}
|
|
13120
|
+
this.sol = false;
|
|
13121
|
+
}
|
|
13122
|
+
const nl = this.buf.indexOf(`
|
|
13123
|
+
`);
|
|
13124
|
+
if (nl !== -1) {
|
|
13125
|
+
const chunk2 = this.buf.slice(0, nl + 1);
|
|
13126
|
+
this.buf = this.buf.slice(nl + 1);
|
|
13127
|
+
this.sol = true;
|
|
13128
|
+
return chunk2;
|
|
13129
|
+
}
|
|
13130
|
+
const chunk = this.buf;
|
|
13131
|
+
this.buf = "";
|
|
13132
|
+
return chunk;
|
|
13133
|
+
}
|
|
13134
|
+
pumpSOL(eof) {
|
|
13135
|
+
const b = this.buf;
|
|
13136
|
+
if (b[0] === `
|
|
13137
|
+
`) {
|
|
13138
|
+
this.buf = b.slice(1);
|
|
13139
|
+
return `
|
|
13140
|
+
`;
|
|
13141
|
+
}
|
|
13142
|
+
if (b[0] === "`") {
|
|
13143
|
+
if (b.length < 3 && !eof)
|
|
13144
|
+
return "";
|
|
13145
|
+
if (b.startsWith("```")) {
|
|
13146
|
+
const nl = b.indexOf(`
|
|
13147
|
+
`, 3);
|
|
13148
|
+
if (nl !== -1) {
|
|
13149
|
+
this.fence = true;
|
|
13150
|
+
const line = b.slice(0, nl + 1);
|
|
13151
|
+
this.buf = b.slice(nl + 1);
|
|
13152
|
+
this.sol = true;
|
|
13153
|
+
return line;
|
|
13154
|
+
}
|
|
13155
|
+
if (eof) {
|
|
13156
|
+
this.buf = "";
|
|
13157
|
+
return b;
|
|
13158
|
+
}
|
|
13159
|
+
return "";
|
|
13160
|
+
}
|
|
13161
|
+
this.sol = false;
|
|
13162
|
+
return "";
|
|
13163
|
+
}
|
|
13164
|
+
if (b[0] === ">") {
|
|
13165
|
+
this.sol = false;
|
|
13166
|
+
return "";
|
|
13167
|
+
}
|
|
13168
|
+
if (b[0] === "#") {
|
|
13169
|
+
let n = 0;
|
|
13170
|
+
while (n < b.length && b[n] === "#")
|
|
13171
|
+
n++;
|
|
13172
|
+
if (n === b.length && !eof)
|
|
13173
|
+
return "";
|
|
13174
|
+
if (n >= 5 && n <= 6 && n < b.length && b[n] === " ") {
|
|
13175
|
+
this.buf = b.slice(n + 1);
|
|
13176
|
+
this.sol = false;
|
|
13177
|
+
return "";
|
|
13178
|
+
}
|
|
13179
|
+
this.sol = false;
|
|
13180
|
+
return "";
|
|
13181
|
+
}
|
|
13182
|
+
if (b[0] === " " || b[0] === "\t") {
|
|
13183
|
+
if (b.search(/[^ \t]/) === -1 && !eof)
|
|
13184
|
+
return "";
|
|
13185
|
+
this.sol = false;
|
|
13186
|
+
return "";
|
|
13187
|
+
}
|
|
13188
|
+
if (b[0] === "-" || b[0] === "*" || b[0] === "_") {
|
|
13189
|
+
const ch = b[0];
|
|
13190
|
+
let j = 0;
|
|
13191
|
+
while (j < b.length && (b[j] === ch || b[j] === " "))
|
|
13192
|
+
j++;
|
|
13193
|
+
if (j === b.length && !eof)
|
|
13194
|
+
return "";
|
|
13195
|
+
if (j === b.length || b[j] === `
|
|
13196
|
+
`) {
|
|
13197
|
+
let count = 0;
|
|
13198
|
+
for (let k = 0;k < j; k++)
|
|
13199
|
+
if (b[k] === ch)
|
|
13200
|
+
count++;
|
|
13201
|
+
if (count >= 3) {
|
|
13202
|
+
if (j < b.length) {
|
|
13203
|
+
this.buf = b.slice(j + 1);
|
|
13204
|
+
this.sol = true;
|
|
13205
|
+
return b.slice(0, j + 1);
|
|
13206
|
+
}
|
|
13207
|
+
this.buf = "";
|
|
13208
|
+
return b;
|
|
13209
|
+
}
|
|
13210
|
+
}
|
|
13211
|
+
this.sol = false;
|
|
13212
|
+
return "";
|
|
13213
|
+
}
|
|
13214
|
+
this.sol = false;
|
|
12635
13215
|
return "";
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
if (
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
13216
|
+
}
|
|
13217
|
+
pumpBody(eof) {
|
|
13218
|
+
let out = "";
|
|
13219
|
+
let i = 0;
|
|
13220
|
+
while (i < this.buf.length) {
|
|
13221
|
+
const c = this.buf[i];
|
|
13222
|
+
if (c === `
|
|
13223
|
+
`) {
|
|
13224
|
+
out += this.buf.slice(0, i + 1);
|
|
13225
|
+
this.buf = this.buf.slice(i + 1);
|
|
13226
|
+
this.sol = true;
|
|
13227
|
+
return out;
|
|
13228
|
+
}
|
|
13229
|
+
if (c === "!" && i + 1 < this.buf.length && this.buf[i + 1] === "[") {
|
|
13230
|
+
out += this.buf.slice(0, i);
|
|
13231
|
+
this.buf = this.buf.slice(i + 2);
|
|
13232
|
+
this.inl = { type: "image", acc: "" };
|
|
13233
|
+
return out;
|
|
13234
|
+
}
|
|
13235
|
+
if (c === "~") {
|
|
13236
|
+
i++;
|
|
13237
|
+
continue;
|
|
12651
13238
|
}
|
|
12652
|
-
if (
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
13239
|
+
if (c === "*") {
|
|
13240
|
+
if (i + 2 < this.buf.length && this.buf[i + 1] === "*" && this.buf[i + 2] === "*") {
|
|
13241
|
+
out += this.buf.slice(0, i);
|
|
13242
|
+
this.buf = this.buf.slice(i + 3);
|
|
13243
|
+
this.inl = { type: "bold3", acc: "" };
|
|
13244
|
+
return out;
|
|
13245
|
+
}
|
|
13246
|
+
if (i + 1 < this.buf.length && this.buf[i + 1] === "*") {
|
|
13247
|
+
i += 2;
|
|
13248
|
+
continue;
|
|
13249
|
+
}
|
|
13250
|
+
if (i + 1 < this.buf.length && this.buf[i + 1] !== " " && this.buf[i + 1] !== `
|
|
13251
|
+
`) {
|
|
13252
|
+
out += this.buf.slice(0, i);
|
|
13253
|
+
this.buf = this.buf.slice(i + 1);
|
|
13254
|
+
this.inl = { type: "italic", acc: "" };
|
|
13255
|
+
return out;
|
|
13256
|
+
}
|
|
13257
|
+
i++;
|
|
13258
|
+
continue;
|
|
13259
|
+
}
|
|
13260
|
+
if (c === "_") {
|
|
13261
|
+
if (i + 2 < this.buf.length && this.buf[i + 1] === "_" && this.buf[i + 2] === "_") {
|
|
13262
|
+
out += this.buf.slice(0, i);
|
|
13263
|
+
this.buf = this.buf.slice(i + 3);
|
|
13264
|
+
this.inl = { type: "ubold3", acc: "" };
|
|
13265
|
+
return out;
|
|
13266
|
+
}
|
|
13267
|
+
if (i + 1 < this.buf.length && this.buf[i + 1] === "_") {
|
|
13268
|
+
i += 2;
|
|
13269
|
+
continue;
|
|
13270
|
+
}
|
|
13271
|
+
if (i + 1 < this.buf.length && this.buf[i + 1] !== " " && this.buf[i + 1] !== `
|
|
13272
|
+
`) {
|
|
13273
|
+
out += this.buf.slice(0, i);
|
|
13274
|
+
this.buf = this.buf.slice(i + 1);
|
|
13275
|
+
this.inl = { type: "uitalic", acc: "" };
|
|
13276
|
+
return out;
|
|
13277
|
+
}
|
|
13278
|
+
i++;
|
|
13279
|
+
continue;
|
|
13280
|
+
}
|
|
13281
|
+
i++;
|
|
12656
13282
|
}
|
|
12657
|
-
|
|
12658
|
-
|
|
13283
|
+
let hold = 0;
|
|
13284
|
+
if (!eof) {
|
|
13285
|
+
if (this.buf.endsWith("**"))
|
|
13286
|
+
hold = 2;
|
|
13287
|
+
else if (this.buf.endsWith("__"))
|
|
13288
|
+
hold = 2;
|
|
13289
|
+
else if (this.buf.endsWith("*"))
|
|
13290
|
+
hold = 1;
|
|
13291
|
+
else if (this.buf.endsWith("_"))
|
|
13292
|
+
hold = 1;
|
|
13293
|
+
else if (this.buf.endsWith("!"))
|
|
13294
|
+
hold = 1;
|
|
13295
|
+
}
|
|
13296
|
+
out += this.buf.slice(0, this.buf.length - hold);
|
|
13297
|
+
this.buf = hold > 0 ? this.buf.slice(-hold) : "";
|
|
13298
|
+
return out;
|
|
13299
|
+
}
|
|
13300
|
+
pumpInline(_eof) {
|
|
13301
|
+
if (!this.inl)
|
|
13302
|
+
return "";
|
|
13303
|
+
this.inl.acc += this.buf;
|
|
13304
|
+
this.buf = "";
|
|
13305
|
+
switch (this.inl.type) {
|
|
13306
|
+
case "bold3": {
|
|
13307
|
+
const idx = this.inl.acc.indexOf("***");
|
|
13308
|
+
if (idx !== -1) {
|
|
13309
|
+
const content = this.inl.acc.slice(0, idx);
|
|
13310
|
+
this.buf = this.inl.acc.slice(idx + 3);
|
|
13311
|
+
this.inl = null;
|
|
13312
|
+
if (StreamingMarkdownFilter.containsCJK(content))
|
|
13313
|
+
return content;
|
|
13314
|
+
return `***${content}***`;
|
|
13315
|
+
}
|
|
13316
|
+
return "";
|
|
13317
|
+
}
|
|
13318
|
+
case "ubold3": {
|
|
13319
|
+
const idx = this.inl.acc.indexOf("___");
|
|
13320
|
+
if (idx !== -1) {
|
|
13321
|
+
const content = this.inl.acc.slice(0, idx);
|
|
13322
|
+
this.buf = this.inl.acc.slice(idx + 3);
|
|
13323
|
+
this.inl = null;
|
|
13324
|
+
if (StreamingMarkdownFilter.containsCJK(content))
|
|
13325
|
+
return content;
|
|
13326
|
+
return `___${content}___`;
|
|
13327
|
+
}
|
|
13328
|
+
return "";
|
|
13329
|
+
}
|
|
13330
|
+
case "italic": {
|
|
13331
|
+
for (let j = 0;j < this.inl.acc.length; j++) {
|
|
13332
|
+
if (this.inl.acc[j] === `
|
|
13333
|
+
`) {
|
|
13334
|
+
const r = "*" + this.inl.acc.slice(0, j + 1);
|
|
13335
|
+
this.buf = this.inl.acc.slice(j + 1);
|
|
13336
|
+
this.inl = null;
|
|
13337
|
+
this.sol = true;
|
|
13338
|
+
return r;
|
|
13339
|
+
}
|
|
13340
|
+
if (this.inl.acc[j] === "*") {
|
|
13341
|
+
if (j + 1 < this.inl.acc.length && this.inl.acc[j + 1] === "*") {
|
|
13342
|
+
j++;
|
|
13343
|
+
continue;
|
|
13344
|
+
}
|
|
13345
|
+
const content = this.inl.acc.slice(0, j);
|
|
13346
|
+
this.buf = this.inl.acc.slice(j + 1);
|
|
13347
|
+
this.inl = null;
|
|
13348
|
+
if (StreamingMarkdownFilter.containsCJK(content))
|
|
13349
|
+
return content;
|
|
13350
|
+
return `*${content}*`;
|
|
13351
|
+
}
|
|
13352
|
+
}
|
|
13353
|
+
return "";
|
|
13354
|
+
}
|
|
13355
|
+
case "uitalic": {
|
|
13356
|
+
for (let j = 0;j < this.inl.acc.length; j++) {
|
|
13357
|
+
if (this.inl.acc[j] === `
|
|
13358
|
+
`) {
|
|
13359
|
+
const r = "_" + this.inl.acc.slice(0, j + 1);
|
|
13360
|
+
this.buf = this.inl.acc.slice(j + 1);
|
|
13361
|
+
this.inl = null;
|
|
13362
|
+
this.sol = true;
|
|
13363
|
+
return r;
|
|
13364
|
+
}
|
|
13365
|
+
if (this.inl.acc[j] === "_") {
|
|
13366
|
+
if (j + 1 < this.inl.acc.length && this.inl.acc[j + 1] === "_") {
|
|
13367
|
+
j++;
|
|
13368
|
+
continue;
|
|
13369
|
+
}
|
|
13370
|
+
const content = this.inl.acc.slice(0, j);
|
|
13371
|
+
this.buf = this.inl.acc.slice(j + 1);
|
|
13372
|
+
this.inl = null;
|
|
13373
|
+
if (StreamingMarkdownFilter.containsCJK(content))
|
|
13374
|
+
return content;
|
|
13375
|
+
return `_${content}_`;
|
|
13376
|
+
}
|
|
13377
|
+
}
|
|
13378
|
+
return "";
|
|
13379
|
+
}
|
|
13380
|
+
case "image": {
|
|
13381
|
+
const cb = this.inl.acc.indexOf("]");
|
|
13382
|
+
if (cb === -1)
|
|
13383
|
+
return "";
|
|
13384
|
+
if (cb + 1 >= this.inl.acc.length)
|
|
13385
|
+
return "";
|
|
13386
|
+
if (this.inl.acc[cb + 1] !== "(") {
|
|
13387
|
+
const r = "![" + this.inl.acc.slice(0, cb + 1);
|
|
13388
|
+
this.buf = this.inl.acc.slice(cb + 1);
|
|
13389
|
+
this.inl = null;
|
|
13390
|
+
return r;
|
|
13391
|
+
}
|
|
13392
|
+
const cp = this.inl.acc.indexOf(")", cb + 2);
|
|
13393
|
+
if (cp !== -1) {
|
|
13394
|
+
this.buf = this.inl.acc.slice(cp + 1);
|
|
13395
|
+
this.inl = null;
|
|
13396
|
+
return "";
|
|
13397
|
+
}
|
|
13398
|
+
return "";
|
|
13399
|
+
}
|
|
12659
13400
|
}
|
|
13401
|
+
return "";
|
|
12660
13402
|
}
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
function extractWeixinMediaDescriptors(itemList) {
|
|
12664
|
-
const out = [];
|
|
12665
|
-
for (const item of itemList ?? []) {
|
|
12666
|
-
const descriptor = descriptorFromItem(item);
|
|
12667
|
-
if (descriptor)
|
|
12668
|
-
out.push(descriptor);
|
|
12669
|
-
const ref = item.type === MessageItemType.TEXT ? item.ref_msg?.message_item : undefined;
|
|
12670
|
-
const refDescriptor = descriptorFromItem(ref);
|
|
12671
|
-
if (refDescriptor)
|
|
12672
|
-
out.push(refDescriptor);
|
|
13403
|
+
static containsCJK(text) {
|
|
13404
|
+
return /[\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/.test(text);
|
|
12673
13405
|
}
|
|
12674
|
-
return out;
|
|
12675
|
-
}
|
|
12676
|
-
function descriptorFromItem(item) {
|
|
12677
|
-
if (!item)
|
|
12678
|
-
return;
|
|
12679
|
-
if (item.type === MessageItemType.IMAGE)
|
|
12680
|
-
return { item, kind: "image" };
|
|
12681
|
-
if (item.type === MessageItemType.VIDEO)
|
|
12682
|
-
return { item, kind: "video" };
|
|
12683
|
-
if (item.type === MessageItemType.FILE)
|
|
12684
|
-
return { item, kind: "file", fileName: item.file_item?.file_name };
|
|
12685
|
-
if (item.type === MessageItemType.VOICE)
|
|
12686
|
-
return { item, kind: "audio" };
|
|
12687
|
-
return;
|
|
12688
13406
|
}
|
|
12689
|
-
var contextTokenStore;
|
|
12690
|
-
var init_inbound = __esm(() => {
|
|
12691
|
-
init_logger();
|
|
12692
|
-
init_random();
|
|
12693
|
-
init_types2();
|
|
12694
|
-
contextTokenStore = new Map;
|
|
12695
|
-
});
|
|
12696
13407
|
|
|
12697
13408
|
// src/weixin/messaging/send.ts
|
|
12698
13409
|
function generateClientId() {
|
|
12699
13410
|
return generateId("openclaw-weixin");
|
|
12700
13411
|
}
|
|
12701
13412
|
function markdownToPlainText(text) {
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
result = result.replace(/!\[[^\]]*\]\([^)]*\)/g, "");
|
|
12705
|
-
result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
|
|
12706
|
-
result = result.replace(/^\|[\s:|-]+\|$/gm, "");
|
|
12707
|
-
result = result.replace(/^\|(.+)\|$/gm, (_, inner) => inner.split("|").map((cell) => cell.trim()).join(" "));
|
|
12708
|
-
result = result.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1");
|
|
12709
|
-
return result;
|
|
13413
|
+
const f = new StreamingMarkdownFilter;
|
|
13414
|
+
return f.feed(text) + f.flush();
|
|
12710
13415
|
}
|
|
12711
13416
|
function buildTextMessageReq(params) {
|
|
12712
13417
|
const { to, text, contextToken, clientId } = params;
|
|
@@ -12942,10 +13647,10 @@ var init_cdn_upload = __esm(() => {
|
|
|
12942
13647
|
|
|
12943
13648
|
// src/weixin/cdn/upload.ts
|
|
12944
13649
|
import crypto3 from "node:crypto";
|
|
12945
|
-
import
|
|
13650
|
+
import fs7 from "node:fs/promises";
|
|
12946
13651
|
async function uploadMediaToCdn(params) {
|
|
12947
13652
|
const { filePath, toUserId, opts, cdnBaseUrl, mediaType, label } = params;
|
|
12948
|
-
const plaintext = await
|
|
13653
|
+
const plaintext = await fs7.readFile(filePath);
|
|
12949
13654
|
const rawsize = plaintext.length;
|
|
12950
13655
|
const rawfilemd5 = crypto3.createHash("md5").update(plaintext).digest("hex");
|
|
12951
13656
|
const filesize = aesEcbPaddedSize(rawsize);
|
|
@@ -13018,7 +13723,7 @@ var init_upload = __esm(() => {
|
|
|
13018
13723
|
});
|
|
13019
13724
|
|
|
13020
13725
|
// src/weixin/messaging/send-media.ts
|
|
13021
|
-
import
|
|
13726
|
+
import path10 from "node:path";
|
|
13022
13727
|
async function sendWeixinMediaFile(params) {
|
|
13023
13728
|
const { media, filePath, to, text, opts, cdnBaseUrl } = params;
|
|
13024
13729
|
const mime = media?.mimeType ?? getMimeFromFilename(filePath);
|
|
@@ -13045,7 +13750,7 @@ async function sendWeixinMediaFile(params) {
|
|
|
13045
13750
|
logger.info(`[weixin] sendWeixinMediaFile: image upload done filekey=${uploaded2.filekey} size=${uploaded2.fileSize}`);
|
|
13046
13751
|
return sendImageMessageWeixin({ to, text, uploaded: uploaded2, opts });
|
|
13047
13752
|
}
|
|
13048
|
-
const fileName = media?.fileName ??
|
|
13753
|
+
const fileName = media?.fileName ?? path10.basename(filePath);
|
|
13049
13754
|
logger.info(`[weixin] sendWeixinMediaFile: uploading file attachment filePath=${filePath} name=${fileName} to=${to}`);
|
|
13050
13755
|
const uploaded = await uploadFileAttachmentToWeixin({
|
|
13051
13756
|
filePath,
|
|
@@ -13065,14 +13770,14 @@ var init_send_media = __esm(() => {
|
|
|
13065
13770
|
});
|
|
13066
13771
|
|
|
13067
13772
|
// src/weixin/messaging/debug-mode.ts
|
|
13068
|
-
import
|
|
13069
|
-
import
|
|
13773
|
+
import fs8 from "node:fs";
|
|
13774
|
+
import path11 from "node:path";
|
|
13070
13775
|
function resolveDebugModePath() {
|
|
13071
|
-
return
|
|
13776
|
+
return path11.join(resolveStateDir(), "openclaw-weixin", "debug-mode.json");
|
|
13072
13777
|
}
|
|
13073
13778
|
function loadState() {
|
|
13074
13779
|
try {
|
|
13075
|
-
const raw =
|
|
13780
|
+
const raw = fs8.readFileSync(resolveDebugModePath(), "utf-8");
|
|
13076
13781
|
const parsed = JSON.parse(raw);
|
|
13077
13782
|
if (parsed && typeof parsed.accounts === "object")
|
|
13078
13783
|
return parsed;
|
|
@@ -13081,8 +13786,8 @@ function loadState() {
|
|
|
13081
13786
|
}
|
|
13082
13787
|
function saveState(state) {
|
|
13083
13788
|
const filePath = resolveDebugModePath();
|
|
13084
|
-
ensureDirSync(
|
|
13085
|
-
|
|
13789
|
+
ensureDirSync(path11.dirname(filePath));
|
|
13790
|
+
fs8.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
13086
13791
|
}
|
|
13087
13792
|
function toggleDebugMode(accountId) {
|
|
13088
13793
|
const state = loadState();
|
|
@@ -13539,9 +14244,9 @@ var init_perf_tracer = __esm(() => {
|
|
|
13539
14244
|
|
|
13540
14245
|
// src/weixin/messaging/handle-weixin-message-turn.ts
|
|
13541
14246
|
import crypto4 from "node:crypto";
|
|
13542
|
-
import
|
|
14247
|
+
import fs9 from "node:fs/promises";
|
|
13543
14248
|
import { tmpdir } from "node:os";
|
|
13544
|
-
import
|
|
14249
|
+
import path12 from "node:path";
|
|
13545
14250
|
function utf8ByteLength(s) {
|
|
13546
14251
|
return Buffer.byteLength(s, "utf8");
|
|
13547
14252
|
}
|
|
@@ -13631,24 +14336,24 @@ function hardCutByCodepoint(s, maxBytes) {
|
|
|
13631
14336
|
return out;
|
|
13632
14337
|
}
|
|
13633
14338
|
function resolveMediaTempDir(customRoot) {
|
|
13634
|
-
return customRoot ??
|
|
14339
|
+
return customRoot ?? path12.join(tmpdir(), "weacpx", "media");
|
|
13635
14340
|
}
|
|
13636
14341
|
function createSaveMediaBuffer(mediaTempDir) {
|
|
13637
14342
|
return async function saveMediaBuffer(buffer, contentType, subdir, maxBytes, originalFilename) {
|
|
13638
14343
|
if (maxBytes !== undefined && buffer.byteLength > maxBytes) {
|
|
13639
14344
|
throw new Error(`media exceeds ${maxBytes} bytes`);
|
|
13640
14345
|
}
|
|
13641
|
-
const dir =
|
|
13642
|
-
await
|
|
14346
|
+
const dir = path12.join(resolveMediaTempDir(mediaTempDir), subdir ?? "");
|
|
14347
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
13643
14348
|
let ext = ".bin";
|
|
13644
14349
|
if (originalFilename) {
|
|
13645
|
-
ext =
|
|
14350
|
+
ext = path12.extname(originalFilename) || ".bin";
|
|
13646
14351
|
} else if (contentType) {
|
|
13647
14352
|
ext = getExtensionFromMime(contentType);
|
|
13648
14353
|
}
|
|
13649
14354
|
const name = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}${ext}`;
|
|
13650
|
-
const filePath =
|
|
13651
|
-
await
|
|
14355
|
+
const filePath = path12.join(dir, name);
|
|
14356
|
+
await fs9.writeFile(filePath, buffer);
|
|
13652
14357
|
return { path: filePath };
|
|
13653
14358
|
};
|
|
13654
14359
|
}
|
|
@@ -13800,7 +14505,7 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
13800
14505
|
continue;
|
|
13801
14506
|
}
|
|
13802
14507
|
try {
|
|
13803
|
-
const buffer = await
|
|
14508
|
+
const buffer = await fs9.readFile(filePath);
|
|
13804
14509
|
const mimeType = downloaded.fileMediaType ?? downloaded.voiceMediaType ?? defaultWeixinMime(descriptor.kind);
|
|
13805
14510
|
media.push(await mediaStore.saveMediaBuffer({
|
|
13806
14511
|
channelId: "weixin",
|
|
@@ -13814,7 +14519,7 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
13814
14519
|
maxBytes: descriptor.kind === "image" ? DEFAULT_IMAGE_MAX_BYTES : DEFAULT_ATTACHMENT_MAX_BYTES
|
|
13815
14520
|
}));
|
|
13816
14521
|
} finally {
|
|
13817
|
-
await
|
|
14522
|
+
await fs9.rm(filePath, { force: true }).catch(() => {});
|
|
13818
14523
|
}
|
|
13819
14524
|
} catch (err) {
|
|
13820
14525
|
deps.errLog(`media download failed: ${String(err)}`);
|
|
@@ -14048,20 +14753,20 @@ var init_handle_weixin_message_turn = __esm(() => {
|
|
|
14048
14753
|
});
|
|
14049
14754
|
|
|
14050
14755
|
// src/weixin/storage/sync-buf.ts
|
|
14051
|
-
import
|
|
14052
|
-
import
|
|
14756
|
+
import fs10 from "node:fs";
|
|
14757
|
+
import path13 from "node:path";
|
|
14053
14758
|
function resolveAccountsDir2() {
|
|
14054
|
-
return
|
|
14759
|
+
return path13.join(resolveStateDir(), "openclaw-weixin", "accounts");
|
|
14055
14760
|
}
|
|
14056
14761
|
function getSyncBufFilePath(accountId) {
|
|
14057
|
-
return
|
|
14762
|
+
return path13.join(resolveAccountsDir2(), `${accountId}.sync.json`);
|
|
14058
14763
|
}
|
|
14059
14764
|
function getLegacySyncBufDefaultJsonPath() {
|
|
14060
|
-
return
|
|
14765
|
+
return path13.join(resolveStateDir(), "agents", "default", "sessions", ".openclaw-weixin-sync", "default.json");
|
|
14061
14766
|
}
|
|
14062
14767
|
function readSyncBufFile(filePath) {
|
|
14063
14768
|
try {
|
|
14064
|
-
const raw =
|
|
14769
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
14065
14770
|
const data = JSON.parse(raw);
|
|
14066
14771
|
if (typeof data.get_updates_buf === "string") {
|
|
14067
14772
|
return data.get_updates_buf;
|
|
@@ -14073,10 +14778,10 @@ function loadGetUpdatesBuf(filePath) {
|
|
|
14073
14778
|
const value = readSyncBufFile(filePath);
|
|
14074
14779
|
if (value !== undefined)
|
|
14075
14780
|
return value;
|
|
14076
|
-
const accountId =
|
|
14781
|
+
const accountId = path13.basename(filePath, ".sync.json");
|
|
14077
14782
|
const rawId = deriveRawAccountId(accountId);
|
|
14078
14783
|
if (rawId) {
|
|
14079
|
-
const compatPath =
|
|
14784
|
+
const compatPath = path13.join(resolveAccountsDir2(), `${rawId}.sync.json`);
|
|
14080
14785
|
const compatValue = readSyncBufFile(compatPath);
|
|
14081
14786
|
if (compatValue !== undefined)
|
|
14082
14787
|
return compatValue;
|
|
@@ -14084,9 +14789,9 @@ function loadGetUpdatesBuf(filePath) {
|
|
|
14084
14789
|
return readSyncBufFile(getLegacySyncBufDefaultJsonPath());
|
|
14085
14790
|
}
|
|
14086
14791
|
function saveGetUpdatesBuf(filePath, getUpdatesBuf) {
|
|
14087
|
-
const dir =
|
|
14792
|
+
const dir = path13.dirname(filePath);
|
|
14088
14793
|
ensureDirSync(dir);
|
|
14089
|
-
|
|
14794
|
+
fs10.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
|
|
14090
14795
|
}
|
|
14091
14796
|
var init_sync_buf = __esm(() => {
|
|
14092
14797
|
init_accounts();
|
|
@@ -14120,23 +14825,23 @@ function shouldFetchTypingConfig(textBody) {
|
|
|
14120
14825
|
}
|
|
14121
14826
|
async function monitorWeixinProvider(opts) {
|
|
14122
14827
|
const {
|
|
14123
|
-
baseUrl,
|
|
14124
|
-
cdnBaseUrl,
|
|
14125
|
-
token,
|
|
14126
|
-
accountId,
|
|
14127
14828
|
agent,
|
|
14128
14829
|
abortSignal,
|
|
14129
14830
|
longPollTimeoutMs
|
|
14130
14831
|
} = opts;
|
|
14832
|
+
let baseUrl = opts.baseUrl;
|
|
14833
|
+
let cdnBaseUrl = opts.cdnBaseUrl;
|
|
14834
|
+
let token = opts.token;
|
|
14835
|
+
let accountId = opts.accountId;
|
|
14131
14836
|
const log = opts.log ?? ((msg) => console.log(msg));
|
|
14132
14837
|
const errLog = (msg) => {
|
|
14133
14838
|
log(msg);
|
|
14134
14839
|
logger.error(msg);
|
|
14135
14840
|
};
|
|
14136
|
-
|
|
14841
|
+
let aLog = logger.withAccount(accountId);
|
|
14137
14842
|
log(`[weixin] monitor started (${baseUrl}, account=${accountId})`);
|
|
14138
14843
|
aLog.info(`Monitor started: baseUrl=${baseUrl}`);
|
|
14139
|
-
|
|
14844
|
+
let syncFilePath = getSyncBufFilePath(accountId);
|
|
14140
14845
|
const previousGetUpdatesBuf = loadGetUpdatesBuf(syncFilePath);
|
|
14141
14846
|
let getUpdatesBuf = previousGetUpdatesBuf ?? "";
|
|
14142
14847
|
if (previousGetUpdatesBuf) {
|
|
@@ -14144,7 +14849,7 @@ async function monitorWeixinProvider(opts) {
|
|
|
14144
14849
|
} else {
|
|
14145
14850
|
log(`[weixin] no previous sync buf, starting fresh`);
|
|
14146
14851
|
}
|
|
14147
|
-
|
|
14852
|
+
let configManager = new WeixinConfigManager({ baseUrl, token }, log);
|
|
14148
14853
|
const conversationExecutor = createConversationExecutor();
|
|
14149
14854
|
const seenMessageIds = new Set;
|
|
14150
14855
|
const messageIdOrder = [];
|
|
@@ -14167,11 +14872,37 @@ async function monitorWeixinProvider(opts) {
|
|
|
14167
14872
|
if (isApiError) {
|
|
14168
14873
|
const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
|
|
14169
14874
|
if (isSessionExpired) {
|
|
14875
|
+
const staleToken = token;
|
|
14876
|
+
const staleAccountId = accountId;
|
|
14877
|
+
errLog(`[weixin] session expired (errcode ${SESSION_EXPIRED_ERRCODE}), entering credential recovery. Please run \`weacpx login\` to re-login.`);
|
|
14170
14878
|
pauseSession(accountId);
|
|
14171
|
-
const pauseMs = getRemainingPauseMs(accountId);
|
|
14172
|
-
errLog(`[weixin] session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing for ${Math.ceil(pauseMs / 60000)} min. Please run \`npx weixin-acp login\` to re-login.`);
|
|
14173
14879
|
consecutiveFailures = 0;
|
|
14174
|
-
await
|
|
14880
|
+
const recovered = await pollForFreshCredentials(staleAccountId, staleToken, log, abortSignal);
|
|
14881
|
+
if (recovered === null) {
|
|
14882
|
+
aLog.info("Monitor stopped (aborted during credential recovery)");
|
|
14883
|
+
return;
|
|
14884
|
+
}
|
|
14885
|
+
const oldAccountId = accountId;
|
|
14886
|
+
accountId = recovered.accountId;
|
|
14887
|
+
baseUrl = recovered.baseUrl;
|
|
14888
|
+
cdnBaseUrl = recovered.cdnBaseUrl;
|
|
14889
|
+
token = recovered.token;
|
|
14890
|
+
aLog = logger.withAccount(accountId);
|
|
14891
|
+
syncFilePath = getSyncBufFilePath(accountId);
|
|
14892
|
+
const previousBuf = loadGetUpdatesBuf(syncFilePath);
|
|
14893
|
+
getUpdatesBuf = previousBuf ?? "";
|
|
14894
|
+
configManager = new WeixinConfigManager({ baseUrl, token }, log);
|
|
14895
|
+
seenMessageIds.clear();
|
|
14896
|
+
messageIdOrder.length = 0;
|
|
14897
|
+
consecutiveFailures = 0;
|
|
14898
|
+
nextTimeoutMs = longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS2;
|
|
14899
|
+
resetSessionPause(oldAccountId);
|
|
14900
|
+
resetSessionPause(accountId);
|
|
14901
|
+
if (oldAccountId !== accountId) {
|
|
14902
|
+
clearContextTokensForAccount(oldAccountId);
|
|
14903
|
+
restoreContextTokens(accountId);
|
|
14904
|
+
}
|
|
14905
|
+
log(`[weixin] credential recovered, resuming monitor with account=${accountId}`);
|
|
14175
14906
|
continue;
|
|
14176
14907
|
}
|
|
14177
14908
|
consecutiveFailures += 1;
|
|
@@ -14265,7 +14996,43 @@ function sleep(ms, signal) {
|
|
|
14265
14996
|
}, { once: true });
|
|
14266
14997
|
});
|
|
14267
14998
|
}
|
|
14268
|
-
|
|
14999
|
+
async function pollForFreshCredentials(staleAccountId, staleToken, log, abortSignal) {
|
|
15000
|
+
let attempt = 0;
|
|
15001
|
+
while (!abortSignal?.aborted) {
|
|
15002
|
+
attempt += 1;
|
|
15003
|
+
const currentAccount = resolveWeixinAccount(staleAccountId);
|
|
15004
|
+
if (currentAccount.token && currentAccount.token !== staleToken) {
|
|
15005
|
+
log(`[weixin] credential recovery: fresh token detected for account=${staleAccountId}`);
|
|
15006
|
+
return {
|
|
15007
|
+
accountId: currentAccount.accountId,
|
|
15008
|
+
baseUrl: currentAccount.baseUrl,
|
|
15009
|
+
cdnBaseUrl: currentAccount.cdnBaseUrl,
|
|
15010
|
+
token: currentAccount.token
|
|
15011
|
+
};
|
|
15012
|
+
}
|
|
15013
|
+
const ids = listWeixinAccountIds();
|
|
15014
|
+
for (const id of ids) {
|
|
15015
|
+
if (id === staleAccountId)
|
|
15016
|
+
continue;
|
|
15017
|
+
const account = resolveWeixinAccount(id);
|
|
15018
|
+
if (account.configured && account.token) {
|
|
15019
|
+
log(`[weixin] credential recovery: new account detected, switching to account=${id}`);
|
|
15020
|
+
return {
|
|
15021
|
+
accountId: account.accountId,
|
|
15022
|
+
baseUrl: account.baseUrl,
|
|
15023
|
+
cdnBaseUrl: account.cdnBaseUrl,
|
|
15024
|
+
token: account.token
|
|
15025
|
+
};
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
if (attempt % 10 === 0) {
|
|
15029
|
+
log(`[weixin] credential recovery: still waiting for fresh credentials (checked ${attempt} times)`);
|
|
15030
|
+
}
|
|
15031
|
+
await sleep(CREDENTIAL_RECOVERY_POLL_INTERVAL_MS, abortSignal);
|
|
15032
|
+
}
|
|
15033
|
+
return null;
|
|
15034
|
+
}
|
|
15035
|
+
var DEFAULT_LONG_POLL_TIMEOUT_MS2 = 35000, MAX_CONSECUTIVE_FAILURES = 3, BACKOFF_DELAY_MS = 30000, RETRY_DELAY_MS = 2000, CREDENTIAL_RECOVERY_POLL_INTERVAL_MS = 30000;
|
|
14269
15036
|
var init_monitor = __esm(() => {
|
|
14270
15037
|
init_api();
|
|
14271
15038
|
init_config_cache();
|
|
@@ -14274,6 +15041,9 @@ var init_monitor = __esm(() => {
|
|
|
14274
15041
|
init_types2();
|
|
14275
15042
|
init_sync_buf();
|
|
14276
15043
|
init_logger();
|
|
15044
|
+
init_accounts();
|
|
15045
|
+
init_session_guard();
|
|
15046
|
+
init_inbound();
|
|
14277
15047
|
});
|
|
14278
15048
|
|
|
14279
15049
|
// src/weixin/bot.ts
|
|
@@ -14332,6 +15102,8 @@ function logout(opts) {
|
|
|
14332
15102
|
log("当前没有已登录的账号");
|
|
14333
15103
|
return;
|
|
14334
15104
|
}
|
|
15105
|
+
for (const id of ids)
|
|
15106
|
+
clearContextTokensForAccount(id);
|
|
14335
15107
|
clearAllWeixinAccounts();
|
|
14336
15108
|
log("✅ 已退出登录");
|
|
14337
15109
|
}
|
|
@@ -14359,6 +15131,7 @@ async function start(agent, opts) {
|
|
|
14359
15131
|
if (!account.configured) {
|
|
14360
15132
|
throw new Error(`账号 ${accountId} 未配置 (缺少 token),请先运行 login`);
|
|
14361
15133
|
}
|
|
15134
|
+
restoreContextTokens(account.accountId);
|
|
14362
15135
|
log(`[weixin] 启动 bot, account=${account.accountId}`);
|
|
14363
15136
|
await monitorWeixinProvider({
|
|
14364
15137
|
baseUrl: account.baseUrl,
|
|
@@ -14384,6 +15157,7 @@ async function start(agent, opts) {
|
|
|
14384
15157
|
var init_bot = __esm(() => {
|
|
14385
15158
|
init_accounts();
|
|
14386
15159
|
init_login_qr();
|
|
15160
|
+
init_inbound();
|
|
14387
15161
|
init_monitor();
|
|
14388
15162
|
});
|
|
14389
15163
|
|
|
@@ -14714,9 +15488,9 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14714
15488
|
}
|
|
14715
15489
|
};
|
|
14716
15490
|
}
|
|
14717
|
-
async function loadLockMetadata(
|
|
15491
|
+
async function loadLockMetadata(path14) {
|
|
14718
15492
|
try {
|
|
14719
|
-
const raw = await readFile6(
|
|
15493
|
+
const raw = await readFile6(path14, "utf8");
|
|
14720
15494
|
const parsed = JSON.parse(raw);
|
|
14721
15495
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
14722
15496
|
return null;
|
|
@@ -15672,6 +16446,18 @@ function parseCommand(input) {
|
|
|
15672
16446
|
return { kind: "workspaces" };
|
|
15673
16447
|
if (command === "/session" && parts[1] === "reset" && parts.length === 2)
|
|
15674
16448
|
return { kind: "session.reset" };
|
|
16449
|
+
if (command === "/session" && parts[1] === "tail") {
|
|
16450
|
+
if (parts.length === 2) {
|
|
16451
|
+
return { kind: "session.tail" };
|
|
16452
|
+
}
|
|
16453
|
+
if (parts.length === 3) {
|
|
16454
|
+
const lines = parsePositiveInt(parts[2]);
|
|
16455
|
+
if (lines !== null) {
|
|
16456
|
+
return { kind: "session.tail", lines };
|
|
16457
|
+
}
|
|
16458
|
+
}
|
|
16459
|
+
return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
|
|
16460
|
+
}
|
|
15675
16461
|
if (command === "/session" && parts[1] === "rm" && parts[2] && parts.length === 3) {
|
|
15676
16462
|
return { kind: "session.rm", alias: parts[2] };
|
|
15677
16463
|
}
|
|
@@ -15774,6 +16560,7 @@ function parseCommand(input) {
|
|
|
15774
16560
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
15775
16561
|
const name = parts[2];
|
|
15776
16562
|
let cwd = "";
|
|
16563
|
+
let raw = false;
|
|
15777
16564
|
let invalid = false;
|
|
15778
16565
|
for (let index = 3;index < parts.length; index += 1) {
|
|
15779
16566
|
if (parts[index] === "--cwd" || parts[index] === "-d") {
|
|
@@ -15785,11 +16572,19 @@ function parseCommand(input) {
|
|
|
15785
16572
|
index += 1;
|
|
15786
16573
|
continue;
|
|
15787
16574
|
}
|
|
16575
|
+
if (parts[index] === "--raw") {
|
|
16576
|
+
if (raw) {
|
|
16577
|
+
invalid = true;
|
|
16578
|
+
break;
|
|
16579
|
+
}
|
|
16580
|
+
raw = true;
|
|
16581
|
+
continue;
|
|
16582
|
+
}
|
|
15788
16583
|
invalid = true;
|
|
15789
16584
|
break;
|
|
15790
16585
|
}
|
|
15791
16586
|
if (!invalid && name.trim().length > 0 && cwd.trim().length > 0) {
|
|
15792
|
-
return { kind: "workspace.new", name, cwd };
|
|
16587
|
+
return { kind: "workspace.new", name, cwd, ...raw ? { raw: true } : {} };
|
|
15793
16588
|
}
|
|
15794
16589
|
}
|
|
15795
16590
|
if (command === "/workspace" && parts[1] === "rm" && parts[2]) {
|
|
@@ -15981,6 +16776,16 @@ function tokenizeCommand(input) {
|
|
|
15981
16776
|
}
|
|
15982
16777
|
return tokens;
|
|
15983
16778
|
}
|
|
16779
|
+
function parsePositiveInt(value) {
|
|
16780
|
+
if (!value)
|
|
16781
|
+
return null;
|
|
16782
|
+
if (!/^\d+$/.test(value))
|
|
16783
|
+
return null;
|
|
16784
|
+
const parsed = Number(value);
|
|
16785
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
16786
|
+
return null;
|
|
16787
|
+
return parsed;
|
|
16788
|
+
}
|
|
15984
16789
|
function parseListFilterFlags(parts, validStatuses) {
|
|
15985
16790
|
const filter = {};
|
|
15986
16791
|
let i = 1;
|
|
@@ -16104,6 +16909,7 @@ var init_command_policy = __esm(() => {
|
|
|
16104
16909
|
"agents",
|
|
16105
16910
|
"workspaces",
|
|
16106
16911
|
"sessions",
|
|
16912
|
+
"session.tail",
|
|
16107
16913
|
"status",
|
|
16108
16914
|
"mode.show",
|
|
16109
16915
|
"replymode.show",
|
|
@@ -16120,6 +16926,7 @@ var init_command_policy = __esm(() => {
|
|
|
16120
16926
|
COMMAND_KIND_TO_LABEL = {
|
|
16121
16927
|
"session.reset": "/clear",
|
|
16122
16928
|
"session.rm": "/session rm",
|
|
16929
|
+
"session.tail": "/session tail",
|
|
16123
16930
|
"replymode.set": "/replymode",
|
|
16124
16931
|
"replymode.reset": "/replymode reset",
|
|
16125
16932
|
"mode.set": "/mode",
|
|
@@ -16237,26 +17044,26 @@ var init_permission_handler = __esm(() => {
|
|
|
16237
17044
|
|
|
16238
17045
|
// src/commands/handlers/config-handler.ts
|
|
16239
17046
|
function handleConfigShow(context) {
|
|
16240
|
-
const lines = ["支持修改的配置字段:", ...SUPPORTED_CONFIG_PATHS.map((
|
|
16241
|
-
lines.push("", "兼容旧配置:", ...LEGACY_CONFIG_PATHS.map((
|
|
17047
|
+
const lines = ["支持修改的配置字段:", ...SUPPORTED_CONFIG_PATHS.map((path14) => `- ${path14}`)];
|
|
17048
|
+
lines.push("", "兼容旧配置:", ...LEGACY_CONFIG_PATHS.map((path14) => `- ${path14}`));
|
|
16242
17049
|
if (context.config) {
|
|
16243
17050
|
lines.push("", "示例:", "- /config set channel.replyMode final", "- /config set logging.level debug");
|
|
16244
17051
|
}
|
|
16245
17052
|
return { text: lines.join(`
|
|
16246
17053
|
`) };
|
|
16247
17054
|
}
|
|
16248
|
-
async function handleConfigSet(context,
|
|
17055
|
+
async function handleConfigSet(context, path14, rawValue) {
|
|
16249
17056
|
if (!context.config || !context.configStore) {
|
|
16250
17057
|
return { text: "当前没有加载可写入的配置。" };
|
|
16251
17058
|
}
|
|
16252
17059
|
const previous = cloneAppConfig(context.config);
|
|
16253
17060
|
const updated = cloneAppConfig(context.config);
|
|
16254
|
-
const result = applySupportedConfigUpdate(updated,
|
|
17061
|
+
const result = applySupportedConfigUpdate(updated, path14, rawValue);
|
|
16255
17062
|
if ("error" in result) {
|
|
16256
17063
|
return { text: result.error };
|
|
16257
17064
|
}
|
|
16258
17065
|
await context.configStore.save(updated);
|
|
16259
|
-
if (
|
|
17066
|
+
if (path14 === "transport.permissionMode" || path14 === "transport.nonInteractivePermissions" || path14 === "transport.permissionPolicy") {
|
|
16260
17067
|
try {
|
|
16261
17068
|
await context.transport.updatePermissionPolicy?.(updated.transport);
|
|
16262
17069
|
} catch (error2) {
|
|
@@ -16266,10 +17073,10 @@ async function handleConfigSet(context, path13, rawValue) {
|
|
|
16266
17073
|
}
|
|
16267
17074
|
}
|
|
16268
17075
|
context.replaceConfig(updated);
|
|
16269
|
-
return { text: `配置已更新:${
|
|
17076
|
+
return { text: `配置已更新:${path14} = ${result.renderedValue}` };
|
|
16270
17077
|
}
|
|
16271
|
-
function applySupportedConfigUpdate(config2,
|
|
16272
|
-
switch (
|
|
17078
|
+
function applySupportedConfigUpdate(config2, path14, rawValue) {
|
|
17079
|
+
switch (path14) {
|
|
16273
17080
|
case "transport.type": {
|
|
16274
17081
|
const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
|
|
16275
17082
|
if (!parsed)
|
|
@@ -16303,6 +17110,11 @@ function applySupportedConfigUpdate(config2, path13, rawValue) {
|
|
|
16303
17110
|
config2.transport.nonInteractivePermissions = parsed;
|
|
16304
17111
|
return { renderedValue: parsed };
|
|
16305
17112
|
}
|
|
17113
|
+
case "transport.permissionPolicy":
|
|
17114
|
+
if (!rawValue.trim())
|
|
17115
|
+
return { error: "transport.permissionPolicy 不能为空。" };
|
|
17116
|
+
config2.transport.permissionPolicy = rawValue;
|
|
17117
|
+
return { renderedValue: rawValue };
|
|
16306
17118
|
case "logging.level": {
|
|
16307
17119
|
const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
|
|
16308
17120
|
if (!parsed)
|
|
@@ -16352,18 +17164,18 @@ function applySupportedConfigUpdate(config2, path13, rawValue) {
|
|
|
16352
17164
|
};
|
|
16353
17165
|
}
|
|
16354
17166
|
}
|
|
16355
|
-
const agentMatch =
|
|
17167
|
+
const agentMatch = path14.match(/^agents\.([^.]+)\.(driver|command)$/);
|
|
16356
17168
|
if (agentMatch) {
|
|
16357
17169
|
const [, name, field] = agentMatch;
|
|
16358
17170
|
if (!name || !field) {
|
|
16359
|
-
return { error: `不支持修改这个配置路径:${
|
|
17171
|
+
return { error: `不支持修改这个配置路径:${path14}` };
|
|
16360
17172
|
}
|
|
16361
17173
|
const agent = config2.agents[name];
|
|
16362
17174
|
if (!agent) {
|
|
16363
17175
|
return { error: `Agent「${name}」不存在,请先创建。` };
|
|
16364
17176
|
}
|
|
16365
17177
|
if (!rawValue.trim()) {
|
|
16366
|
-
return { error: `${
|
|
17178
|
+
return { error: `${path14} 不能为空。` };
|
|
16367
17179
|
}
|
|
16368
17180
|
if (field === "driver") {
|
|
16369
17181
|
agent.driver = rawValue;
|
|
@@ -16372,18 +17184,18 @@ function applySupportedConfigUpdate(config2, path13, rawValue) {
|
|
|
16372
17184
|
}
|
|
16373
17185
|
return { renderedValue: rawValue };
|
|
16374
17186
|
}
|
|
16375
|
-
const workspaceMatch =
|
|
17187
|
+
const workspaceMatch = path14.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
|
|
16376
17188
|
if (workspaceMatch) {
|
|
16377
17189
|
const [, name, field] = workspaceMatch;
|
|
16378
17190
|
if (!name || !field) {
|
|
16379
|
-
return { error: `不支持修改这个配置路径:${
|
|
17191
|
+
return { error: `不支持修改这个配置路径:${path14}` };
|
|
16380
17192
|
}
|
|
16381
17193
|
const workspace = config2.workspaces[name];
|
|
16382
17194
|
if (!workspace) {
|
|
16383
17195
|
return { error: `工作区「${name}」不存在,请先创建。` };
|
|
16384
17196
|
}
|
|
16385
17197
|
if (!rawValue.trim()) {
|
|
16386
|
-
return { error: `${
|
|
17198
|
+
return { error: `${path14} 不能为空。` };
|
|
16387
17199
|
}
|
|
16388
17200
|
if (field === "cwd") {
|
|
16389
17201
|
workspace.cwd = rawValue;
|
|
@@ -16392,15 +17204,15 @@ function applySupportedConfigUpdate(config2, path13, rawValue) {
|
|
|
16392
17204
|
}
|
|
16393
17205
|
return { renderedValue: rawValue };
|
|
16394
17206
|
}
|
|
16395
|
-
return { error: `不支持修改这个配置路径:${
|
|
17207
|
+
return { error: `不支持修改这个配置路径:${path14}` };
|
|
16396
17208
|
}
|
|
16397
17209
|
function parseEnum(value, allowed) {
|
|
16398
17210
|
return allowed.includes(value) ? value : null;
|
|
16399
17211
|
}
|
|
16400
|
-
function parsePositiveNumber(rawValue,
|
|
17212
|
+
function parsePositiveNumber(rawValue, path14) {
|
|
16401
17213
|
const value = Number(rawValue);
|
|
16402
17214
|
if (!Number.isFinite(value) || value <= 0) {
|
|
16403
|
-
return { error: `${
|
|
17215
|
+
return { error: `${path14} 必须是正数。` };
|
|
16404
17216
|
}
|
|
16405
17217
|
return { value };
|
|
16406
17218
|
}
|
|
@@ -16412,6 +17224,7 @@ var init_config_handler = __esm(() => {
|
|
|
16412
17224
|
"transport.sessionInitTimeoutMs",
|
|
16413
17225
|
"transport.permissionMode",
|
|
16414
17226
|
"transport.nonInteractivePermissions",
|
|
17227
|
+
"transport.permissionPolicy",
|
|
16415
17228
|
"logging.level",
|
|
16416
17229
|
"logging.maxSizeBytes",
|
|
16417
17230
|
"logging.maxFiles",
|
|
@@ -16834,7 +17647,7 @@ async function handleSessionAttach(context, chatKey, alias, agent, workspace, tr
|
|
|
16834
17647
|
return {
|
|
16835
17648
|
text: [
|
|
16836
17649
|
"没有找到可绑定的已有会话。",
|
|
16837
|
-
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent} --ws ${workspace} --name <会话名>`
|
|
17650
|
+
`请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent} --ws ${quoteWorkspaceNameIfNeeded(workspace)} --name <会话名>`
|
|
16838
17651
|
].join(`
|
|
16839
17652
|
`)
|
|
16840
17653
|
};
|
|
@@ -16966,6 +17779,15 @@ async function handleCancel(context, chatKey) {
|
|
|
16966
17779
|
async function handleSessionReset(context, chatKey) {
|
|
16967
17780
|
return await context.lifecycle.resetCurrentSession(chatKey);
|
|
16968
17781
|
}
|
|
17782
|
+
async function handleSessionTail(context, chatKey, lines) {
|
|
17783
|
+
const session = await context.sessions.getCurrentSession(chatKey);
|
|
17784
|
+
if (!session) {
|
|
17785
|
+
return { text: NO_CURRENT_SESSION_TEXT };
|
|
17786
|
+
}
|
|
17787
|
+
const resolvedLines = Math.min(Math.max(lines ?? DEFAULT_SESSION_TAIL_LINES, 1), MAX_SESSION_TAIL_LINES);
|
|
17788
|
+
const result = await context.transport.tailSessionHistory(session, resolvedLines);
|
|
17789
|
+
return { text: result.text };
|
|
17790
|
+
}
|
|
16969
17791
|
async function handleSessionRemove(context, chatKey, alias) {
|
|
16970
17792
|
const internalAlias = await context.sessions.resolveAliasForChat(chatKey, alias);
|
|
16971
17793
|
const session = await context.sessions.getSession(internalAlias);
|
|
@@ -17036,7 +17858,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
17036
17858
|
return { text: lines.join(`
|
|
17037
17859
|
`) };
|
|
17038
17860
|
}
|
|
17039
|
-
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
17861
|
+
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
17040
17862
|
const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
|
|
17041
17863
|
if (!session.replyMode)
|
|
17042
17864
|
session.replyMode = effectiveReplyMode;
|
|
@@ -17061,7 +17883,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
17061
17883
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
|
|
17062
17884
|
try {
|
|
17063
17885
|
const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
|
|
17064
|
-
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, perfSpan);
|
|
17886
|
+
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
17065
17887
|
if (claimHumanReply) {
|
|
17066
17888
|
try {
|
|
17067
17889
|
await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
|
|
@@ -17081,17 +17903,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
17081
17903
|
throw error2;
|
|
17082
17904
|
}
|
|
17083
17905
|
}
|
|
17084
|
-
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
17906
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
17085
17907
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
17086
17908
|
if (!session) {
|
|
17087
17909
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
17088
17910
|
}
|
|
17089
17911
|
try {
|
|
17090
|
-
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
17912
|
+
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
17091
17913
|
} catch (error2) {
|
|
17092
17914
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
|
|
17093
17915
|
if (recovered) {
|
|
17094
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
17916
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
17095
17917
|
}
|
|
17096
17918
|
return context.recovery.renderTransportError(session, error2);
|
|
17097
17919
|
}
|
|
@@ -17158,10 +17980,11 @@ async function markCoordinatorResultsInjectionFailed(context, taskIds, groupIds,
|
|
|
17158
17980
|
});
|
|
17159
17981
|
}
|
|
17160
17982
|
}
|
|
17161
|
-
var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
|
|
17983
|
+
var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", DEFAULT_SESSION_TAIL_LINES = 50, MAX_SESSION_TAIL_LINES = 500, sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
|
|
17162
17984
|
var init_session_handler = __esm(() => {
|
|
17163
17985
|
init_build_coordinator_prompt();
|
|
17164
17986
|
init_channel_scope();
|
|
17987
|
+
init_workspace_name();
|
|
17165
17988
|
sessionHelp = {
|
|
17166
17989
|
topic: "session",
|
|
17167
17990
|
aliases: ["ss", "sessions"],
|
|
@@ -17173,6 +17996,7 @@ var init_session_handler = __esm(() => {
|
|
|
17173
17996
|
{ usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
|
|
17174
17997
|
{ usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
|
|
17175
17998
|
{ usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
|
|
17999
|
+
{ usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
|
|
17176
18000
|
{ usage: "/session rm <alias>", description: "删除逻辑会话" },
|
|
17177
18001
|
{ usage: "/use <alias>", description: "切换当前会话" },
|
|
17178
18002
|
{ usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
|
|
@@ -17808,7 +18632,7 @@ var init_agent_handler = __esm(() => {
|
|
|
17808
18632
|
function handleWorkspaces(context) {
|
|
17809
18633
|
return { text: context.config ? renderWorkspaces(context.config) : "No config loaded." };
|
|
17810
18634
|
}
|
|
17811
|
-
async function handleWorkspaceCreate(context, workspaceName, cwd) {
|
|
18635
|
+
async function handleWorkspaceCreate(context, workspaceName, cwd, options = {}) {
|
|
17812
18636
|
if (!context.config || !context.configStore) {
|
|
17813
18637
|
return { text: "当前没有加载可写入的配置。" };
|
|
17814
18638
|
}
|
|
@@ -17816,9 +18640,18 @@ async function handleWorkspaceCreate(context, workspaceName, cwd) {
|
|
|
17816
18640
|
if (!await pathExists(normalizedCwd)) {
|
|
17817
18641
|
return { text: `工作区路径不存在:${cwd}` };
|
|
17818
18642
|
}
|
|
17819
|
-
|
|
18643
|
+
let name = workspaceName;
|
|
18644
|
+
let notice;
|
|
18645
|
+
if (!options.raw && !isWorkspaceNameValid(workspaceName)) {
|
|
18646
|
+
const base = sanitizeWorkspaceName(workspaceName);
|
|
18647
|
+
name = allocateWorkspaceName(base, context.config.workspaces);
|
|
18648
|
+
notice = `名称 ${JSON.stringify(workspaceName)} 含有特殊字符,已保存为「${name}」。如需保留原名请加 --raw。`;
|
|
18649
|
+
}
|
|
18650
|
+
const updated = await context.configStore.upsertWorkspace(name, normalizedCwd);
|
|
17820
18651
|
context.replaceConfig(updated);
|
|
17821
|
-
|
|
18652
|
+
const savedLine = `工作区「${name}」已保存`;
|
|
18653
|
+
return { text: notice ? `${notice}
|
|
18654
|
+
${savedLine}` : savedLine };
|
|
17822
18655
|
}
|
|
17823
18656
|
async function handleWorkspaceRemove(context, workspaceName) {
|
|
17824
18657
|
if (!context.config || !context.configStore) {
|
|
@@ -17830,6 +18663,7 @@ async function handleWorkspaceRemove(context, workspaceName) {
|
|
|
17830
18663
|
}
|
|
17831
18664
|
var workspaceHelp;
|
|
17832
18665
|
var init_workspace_handler = __esm(() => {
|
|
18666
|
+
init_workspace_name();
|
|
17833
18667
|
init_workspace_path();
|
|
17834
18668
|
workspaceHelp = {
|
|
17835
18669
|
topic: "workspace",
|
|
@@ -17838,7 +18672,7 @@ var init_workspace_handler = __esm(() => {
|
|
|
17838
18672
|
commands: [
|
|
17839
18673
|
{ usage: "/workspaces", description: "查看当前已注册的工作区" },
|
|
17840
18674
|
{ usage: "/workspace 或 /ws", description: "查看工作区列表" },
|
|
17841
|
-
{ usage: "/ws new <name> -d <path>", description: "
|
|
18675
|
+
{ usage: "/ws new <name> -d <path> [--raw]", description: "添加工作区;含特殊字符的名称会被自动规范化,--raw 保留原名" },
|
|
17842
18676
|
{ usage: "/workspace rm <name>", description: "删除工作区" }
|
|
17843
18677
|
],
|
|
17844
18678
|
examples: ['/ws new backend -d "/tmp/backend"', "/workspace rm backend"]
|
|
@@ -18075,7 +18909,7 @@ async function resolveShortcutWorkspace(context, target) {
|
|
|
18075
18909
|
reused: true
|
|
18076
18910
|
};
|
|
18077
18911
|
}
|
|
18078
|
-
const workspaceName = allocateWorkspaceName(
|
|
18912
|
+
const workspaceName = allocateWorkspaceName(sanitizeWorkspaceName(basenameForWorkspacePath(cwd)), context.config?.workspaces ?? {});
|
|
18079
18913
|
const updated = await context.configStore.upsertWorkspace(workspaceName, cwd);
|
|
18080
18914
|
context.replaceConfig(updated);
|
|
18081
18915
|
return {
|
|
@@ -18084,16 +18918,6 @@ async function resolveShortcutWorkspace(context, target) {
|
|
|
18084
18918
|
reused: false
|
|
18085
18919
|
};
|
|
18086
18920
|
}
|
|
18087
|
-
function allocateWorkspaceName(context, baseName) {
|
|
18088
|
-
if (!context.config?.workspaces[baseName]) {
|
|
18089
|
-
return baseName;
|
|
18090
|
-
}
|
|
18091
|
-
let suffix = 2;
|
|
18092
|
-
while (context.config.workspaces[`${baseName}-${suffix}`]) {
|
|
18093
|
-
suffix += 1;
|
|
18094
|
-
}
|
|
18095
|
-
return `${baseName}-${suffix}`;
|
|
18096
|
-
}
|
|
18097
18921
|
async function allocateUniqueSessionAlias(context, baseAlias, chatKey) {
|
|
18098
18922
|
if (!await hasLogicalSession(context, baseAlias, chatKey)) {
|
|
18099
18923
|
return baseAlias;
|
|
@@ -18119,6 +18943,7 @@ function renderShortcutSessionCreationError(workspace, alias) {
|
|
|
18119
18943
|
};
|
|
18120
18944
|
}
|
|
18121
18945
|
var init_session_shortcut_handler = __esm(() => {
|
|
18946
|
+
init_workspace_name();
|
|
18122
18947
|
init_workspace_path();
|
|
18123
18948
|
init_errors();
|
|
18124
18949
|
init_channel_scope();
|
|
@@ -18131,8 +18956,8 @@ function renderTransportError(session, error2) {
|
|
|
18131
18956
|
return {
|
|
18132
18957
|
text: [
|
|
18133
18958
|
`当前会话「${session.alias}」暂时不可用。`,
|
|
18134
|
-
`请先在微信里重新执行:/session new ${session.alias} --agent ${session.agent} --ws ${session.workspace}`,
|
|
18135
|
-
`如果你要绑定一个已有会话,再执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
|
|
18959
|
+
`请先在微信里重新执行:/session new ${session.alias} --agent ${session.agent} --ws ${quoteWorkspaceNameIfNeeded(session.workspace)}`,
|
|
18960
|
+
`如果你要绑定一个已有会话,再执行:/session attach ${session.alias} --agent ${session.agent} --ws ${quoteWorkspaceNameIfNeeded(session.workspace)} --name <会话名>`
|
|
18136
18961
|
].join(`
|
|
18137
18962
|
`)
|
|
18138
18963
|
};
|
|
@@ -18194,7 +19019,7 @@ function renderSessionCreationFailure(session, detail) {
|
|
|
18194
19019
|
text: [
|
|
18195
19020
|
"会话创建失败。",
|
|
18196
19021
|
`错误信息:${summarizeTransportError(detail)}`,
|
|
18197
|
-
`如果你要先绑定一个已有会话,可以执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
|
|
19022
|
+
`如果你要先绑定一个已有会话,可以执行:/session attach ${session.alias} --agent ${session.agent} --ws ${quoteWorkspaceNameIfNeeded(session.workspace)} --name <会话名>`
|
|
18198
19023
|
].join(`
|
|
18199
19024
|
`)
|
|
18200
19025
|
};
|
|
@@ -18213,6 +19038,7 @@ async function tryRecoverMissingSession(ops, session, error2) {
|
|
|
18213
19038
|
}
|
|
18214
19039
|
var init_session_recovery_handler = __esm(() => {
|
|
18215
19040
|
init_errors();
|
|
19041
|
+
init_workspace_name();
|
|
18216
19042
|
});
|
|
18217
19043
|
|
|
18218
19044
|
// src/recovery/auto-install-optional-dep.ts
|
|
@@ -18338,10 +19164,10 @@ ${err.message}`, reason: "spawn" });
|
|
|
18338
19164
|
const dir = join10(homedir6(), ".weacpx", "logs");
|
|
18339
19165
|
await mkdir10(dir, { recursive: true });
|
|
18340
19166
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
|
|
18341
|
-
const
|
|
18342
|
-
const stream = createWriteStream(
|
|
19167
|
+
const path14 = join10(dir, `auto-install-${timestamp}.log`);
|
|
19168
|
+
const stream = createWriteStream(path14, { flags: "a" });
|
|
18343
19169
|
return {
|
|
18344
|
-
path:
|
|
19170
|
+
path: path14,
|
|
18345
19171
|
append: async (chunk) => {
|
|
18346
19172
|
await new Promise((resolve3, reject) => stream.write(chunk, (err) => err ? reject(err) : resolve3()));
|
|
18347
19173
|
},
|
|
@@ -18424,9 +19250,9 @@ function isUnder(child, parent) {
|
|
|
18424
19250
|
const p = parent.replace(/[\\/]+$/, "");
|
|
18425
19251
|
return c === p || c.startsWith(p + "/") || c.startsWith(p + "\\");
|
|
18426
19252
|
}
|
|
18427
|
-
async function defaultFsExists(
|
|
19253
|
+
async function defaultFsExists(path14) {
|
|
18428
19254
|
try {
|
|
18429
|
-
await access3(
|
|
19255
|
+
await access3(path14);
|
|
18430
19256
|
return true;
|
|
18431
19257
|
} catch {
|
|
18432
19258
|
return false;
|
|
@@ -18595,7 +19421,7 @@ class CommandRouter {
|
|
|
18595
19421
|
this.quota = quota;
|
|
18596
19422
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
18597
19423
|
}
|
|
18598
|
-
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, perfSpan) {
|
|
19424
|
+
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
18599
19425
|
const startedAt = Date.now();
|
|
18600
19426
|
const command = parseCommand(input);
|
|
18601
19427
|
await this.logger.debug("command.parsed", "parsed inbound command", {
|
|
@@ -18654,7 +19480,7 @@ class CommandRouter {
|
|
|
18654
19480
|
case "workspaces":
|
|
18655
19481
|
return handleWorkspaces(this.createHandlerContext());
|
|
18656
19482
|
case "workspace.new":
|
|
18657
|
-
return await handleWorkspaceCreate(this.createHandlerContext(), command.name, command.cwd);
|
|
19483
|
+
return await handleWorkspaceCreate(this.createHandlerContext(), command.name, command.cwd, command.raw ? { raw: true } : {});
|
|
18658
19484
|
case "workspace.rm":
|
|
18659
19485
|
return await handleWorkspaceRemove(this.createHandlerContext(), command.name);
|
|
18660
19486
|
case "sessions":
|
|
@@ -18685,6 +19511,8 @@ class CommandRouter {
|
|
|
18685
19511
|
return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18686
19512
|
case "session.reset":
|
|
18687
19513
|
return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
|
|
19514
|
+
case "session.tail":
|
|
19515
|
+
return await handleSessionTail(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.lines);
|
|
18688
19516
|
case "session.rm":
|
|
18689
19517
|
return await handleSessionRemove(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18690
19518
|
case "groups":
|
|
@@ -18712,7 +19540,7 @@ class CommandRouter {
|
|
|
18712
19540
|
case "task.cancel":
|
|
18713
19541
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
18714
19542
|
case "prompt":
|
|
18715
|
-
return await handlePrompt(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
19543
|
+
return await handlePrompt(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
18716
19544
|
}
|
|
18717
19545
|
});
|
|
18718
19546
|
}
|
|
@@ -18765,7 +19593,7 @@ class CommandRouter {
|
|
|
18765
19593
|
return {
|
|
18766
19594
|
setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
|
|
18767
19595
|
cancelTransportSession: (session) => this.cancelTransportSession(session),
|
|
18768
|
-
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride ?? perfSpan)
|
|
19596
|
+
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan)
|
|
18769
19597
|
};
|
|
18770
19598
|
}
|
|
18771
19599
|
createSessionRenderRecoveryOps() {
|
|
@@ -18943,7 +19771,7 @@ class CommandRouter {
|
|
|
18943
19771
|
async checkTransportSession(session) {
|
|
18944
19772
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
18945
19773
|
}
|
|
18946
|
-
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpan) {
|
|
19774
|
+
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
18947
19775
|
session.mcpCoordinatorSession ??= session.transportSession;
|
|
18948
19776
|
let done = false;
|
|
18949
19777
|
let abortRequested = false;
|
|
@@ -18999,7 +19827,8 @@ class CommandRouter {
|
|
|
18999
19827
|
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
|
|
19000
19828
|
...media ? { media } : {},
|
|
19001
19829
|
...reply ? { onSegment } : {},
|
|
19002
|
-
...onToolEvent ? { onToolEvent } : {}
|
|
19830
|
+
...onToolEvent ? { onToolEvent } : {},
|
|
19831
|
+
...onThought ? { onThought } : {}
|
|
19003
19832
|
}));
|
|
19004
19833
|
} catch (error2) {
|
|
19005
19834
|
localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
|
|
@@ -19113,7 +19942,7 @@ function resolveAcpxCommandMetadata(options = {}) {
|
|
|
19113
19942
|
}
|
|
19114
19943
|
const platform = options.platform ?? process.platform;
|
|
19115
19944
|
const resolvePackageJson = options.resolvePackageJson ?? ((id) => require3.resolve(id));
|
|
19116
|
-
const readPackageJson = options.readPackageJson ?? ((
|
|
19945
|
+
const readPackageJson = options.readPackageJson ?? ((path14) => JSON.parse(readFileSync(path14, "utf8")));
|
|
19117
19946
|
try {
|
|
19118
19947
|
const packageJsonPath = resolvePackageJson("acpx/package.json");
|
|
19119
19948
|
const pkg = readPackageJson(packageJsonPath);
|
|
@@ -19165,7 +19994,7 @@ class ConsoleAgent {
|
|
|
19165
19994
|
...m.fileName ? { fileName: m.fileName } : {}
|
|
19166
19995
|
})) : undefined;
|
|
19167
19996
|
request.perfSpan?.mark("agent.dispatched");
|
|
19168
|
-
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.perfSpan);
|
|
19997
|
+
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.onThought, request.perfSpan);
|
|
19169
19998
|
}
|
|
19170
19999
|
isKnownCommand(text) {
|
|
19171
20000
|
return isKnownWeacpxCommandText(text);
|
|
@@ -19490,8 +20319,8 @@ class OrchestrationServer {
|
|
|
19490
20319
|
if (this.endpoint.kind !== "unix") {
|
|
19491
20320
|
return;
|
|
19492
20321
|
}
|
|
19493
|
-
const removeFile = this.deps.removeFile ?? (async (
|
|
19494
|
-
await rm7(
|
|
20322
|
+
const removeFile = this.deps.removeFile ?? (async (path14) => {
|
|
20323
|
+
await rm7(path14, { force: true });
|
|
19495
20324
|
});
|
|
19496
20325
|
await removeFile(this.endpoint.path);
|
|
19497
20326
|
}
|
|
@@ -19652,9 +20481,9 @@ function requireTaskQuestions(params, key) {
|
|
|
19652
20481
|
};
|
|
19653
20482
|
});
|
|
19654
20483
|
}
|
|
19655
|
-
async function canConnectToEndpoint(
|
|
20484
|
+
async function canConnectToEndpoint(path14) {
|
|
19656
20485
|
return await new Promise((resolve3) => {
|
|
19657
|
-
const socket = createConnection2(
|
|
20486
|
+
const socket = createConnection2(path14);
|
|
19658
20487
|
let settled = false;
|
|
19659
20488
|
const finish = (result) => {
|
|
19660
20489
|
if (settled) {
|
|
@@ -19675,7 +20504,7 @@ async function canConnectToEndpoint(path13) {
|
|
|
19675
20504
|
});
|
|
19676
20505
|
});
|
|
19677
20506
|
}
|
|
19678
|
-
async function listen(server,
|
|
20507
|
+
async function listen(server, path14) {
|
|
19679
20508
|
await new Promise((resolve3, reject) => {
|
|
19680
20509
|
const onError = (error2) => {
|
|
19681
20510
|
server.off("listening", onListening);
|
|
@@ -19687,7 +20516,7 @@ async function listen(server, path13) {
|
|
|
19687
20516
|
};
|
|
19688
20517
|
server.once("error", onError);
|
|
19689
20518
|
server.once("listening", onListening);
|
|
19690
|
-
server.listen(
|
|
20519
|
+
server.listen(path14);
|
|
19691
20520
|
});
|
|
19692
20521
|
}
|
|
19693
20522
|
function isServerNotRunningError(error2) {
|
|
@@ -19811,6 +20640,7 @@ class OrchestrationService {
|
|
|
19811
20640
|
stateMutex;
|
|
19812
20641
|
pendingWorkerSessions = new Map;
|
|
19813
20642
|
pendingLogicalTransportSessions = new Map;
|
|
20643
|
+
pendingParallelStarts = new Map;
|
|
19814
20644
|
constructor(deps) {
|
|
19815
20645
|
this.deps = deps;
|
|
19816
20646
|
this.stateMutex = deps.stateMutex ?? new AsyncMutex;
|
|
@@ -19975,84 +20805,139 @@ class OrchestrationService {
|
|
|
19975
20805
|
const normalizedGroupId = this.normalizeGroupId(input.groupId);
|
|
19976
20806
|
const taskId = this.deps.createId();
|
|
19977
20807
|
const workerSession = await this.resolveWorkerSession(input);
|
|
19978
|
-
|
|
19979
|
-
|
|
19980
|
-
let prepared;
|
|
19981
|
-
try {
|
|
19982
|
-
ensuredWorkerSession = await this.ensureReservedWorkerSession({
|
|
19983
|
-
workerSession,
|
|
19984
|
-
sourceHandle: input.sourceHandle,
|
|
19985
|
-
sourceKind: input.sourceKind,
|
|
19986
|
-
coordinatorSession: input.coordinatorSession,
|
|
19987
|
-
workspace: input.workspace,
|
|
19988
|
-
...input.cwd ? { cwd: input.cwd } : {},
|
|
19989
|
-
targetAgent: input.targetAgent,
|
|
19990
|
-
role
|
|
19991
|
-
});
|
|
19992
|
-
prepared = await this.mutate(async () => {
|
|
20808
|
+
if (input.parallel) {
|
|
20809
|
+
const queuedResult = await this.mutate(async () => {
|
|
19993
20810
|
const state = await this.deps.loadState();
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
20811
|
+
if (this.canStartParallelTask(state, input.targetAgent)) {
|
|
20812
|
+
this.pendingParallelStarts.set(input.targetAgent, (this.pendingParallelStarts.get(input.targetAgent) ?? 0) + 1);
|
|
20813
|
+
return null;
|
|
19997
20814
|
}
|
|
19998
|
-
const
|
|
20815
|
+
const now = this.deps.now().toISOString();
|
|
20816
|
+
const queuedTask = {
|
|
19999
20817
|
taskId,
|
|
20000
20818
|
sourceHandle: input.sourceHandle,
|
|
20001
20819
|
sourceKind: input.sourceKind,
|
|
20002
20820
|
coordinatorSession: input.coordinatorSession,
|
|
20003
|
-
workerSession
|
|
20821
|
+
workerSession,
|
|
20004
20822
|
workspace: input.workspace,
|
|
20005
20823
|
...input.cwd ? { cwd: input.cwd } : {},
|
|
20006
20824
|
targetAgent: input.targetAgent,
|
|
20007
20825
|
...role ? { role } : {},
|
|
20008
20826
|
...normalizedGroupId ? { groupId: normalizedGroupId } : {},
|
|
20009
20827
|
task: input.task,
|
|
20010
|
-
status: "
|
|
20828
|
+
status: "queued",
|
|
20829
|
+
ephemeralWorkerSession: true,
|
|
20011
20830
|
summary: "",
|
|
20012
20831
|
resultText: "",
|
|
20013
20832
|
createdAt: now,
|
|
20014
20833
|
updatedAt: now,
|
|
20015
20834
|
eventSeq: 1,
|
|
20016
|
-
events: [{ seq: 1, at: now, type: "created", status: "
|
|
20835
|
+
events: [{ seq: 1, at: now, type: "created", status: "queued", message: "Task queued at parallel capacity" }],
|
|
20017
20836
|
...input.chatKey ? { chatKey: input.chatKey } : {},
|
|
20018
20837
|
...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
|
|
20019
20838
|
...input.accountId ? { accountId: input.accountId } : {}
|
|
20020
20839
|
};
|
|
20021
|
-
|
|
20022
|
-
|
|
20023
|
-
|
|
20024
|
-
|
|
20025
|
-
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20032
|
-
|
|
20033
|
-
this.
|
|
20034
|
-
|
|
20035
|
-
|
|
20036
|
-
|
|
20840
|
+
state.orchestration.tasks[taskId] = queuedTask;
|
|
20841
|
+
await this.deps.saveState(state);
|
|
20842
|
+
return { taskId, status: "queued", workerSession };
|
|
20843
|
+
});
|
|
20844
|
+
if (queuedResult) {
|
|
20845
|
+
this.logEvent("orchestration.task.queued", "parallel task queued at capacity", { taskId, targetAgent: input.targetAgent });
|
|
20846
|
+
return queuedResult;
|
|
20847
|
+
}
|
|
20848
|
+
}
|
|
20849
|
+
const releasePendingParallelStart = input.parallel ? () => {
|
|
20850
|
+
const count = this.pendingParallelStarts.get(input.targetAgent) ?? 0;
|
|
20851
|
+
if (count <= 1) {
|
|
20852
|
+
this.pendingParallelStarts.delete(input.targetAgent);
|
|
20853
|
+
} else {
|
|
20854
|
+
this.pendingParallelStarts.set(input.targetAgent, count - 1);
|
|
20855
|
+
}
|
|
20856
|
+
} : undefined;
|
|
20857
|
+
let ensuredWorkerSession = workerSession;
|
|
20858
|
+
let prepared;
|
|
20859
|
+
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSession);
|
|
20860
|
+
try {
|
|
20861
|
+
try {
|
|
20862
|
+
ensuredWorkerSession = await this.ensureReservedWorkerSession({
|
|
20863
|
+
workerSession,
|
|
20864
|
+
sourceHandle: input.sourceHandle,
|
|
20865
|
+
sourceKind: input.sourceKind,
|
|
20037
20866
|
coordinatorSession: input.coordinatorSession,
|
|
20038
20867
|
workspace: input.workspace,
|
|
20039
20868
|
...input.cwd ? { cwd: input.cwd } : {},
|
|
20040
20869
|
targetAgent: input.targetAgent,
|
|
20041
20870
|
role
|
|
20042
|
-
};
|
|
20043
|
-
await this.
|
|
20044
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
20048
|
-
|
|
20049
|
-
|
|
20050
|
-
|
|
20051
|
-
|
|
20871
|
+
});
|
|
20872
|
+
prepared = await this.mutate(async () => {
|
|
20873
|
+
const state = await this.deps.loadState();
|
|
20874
|
+
const now = this.deps.now().toISOString();
|
|
20875
|
+
if (normalizedGroupId) {
|
|
20876
|
+
this.assertGroupOwnership(this.ensureGroups(state)[normalizedGroupId], normalizedGroupId, input.coordinatorSession);
|
|
20877
|
+
}
|
|
20878
|
+
const task = {
|
|
20879
|
+
taskId,
|
|
20880
|
+
sourceHandle: input.sourceHandle,
|
|
20881
|
+
sourceKind: input.sourceKind,
|
|
20882
|
+
coordinatorSession: input.coordinatorSession,
|
|
20883
|
+
workerSession: ensuredWorkerSession,
|
|
20884
|
+
workspace: input.workspace,
|
|
20885
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
20886
|
+
targetAgent: input.targetAgent,
|
|
20887
|
+
...role ? { role } : {},
|
|
20888
|
+
...normalizedGroupId ? { groupId: normalizedGroupId } : {},
|
|
20889
|
+
task: input.task,
|
|
20890
|
+
status: "running",
|
|
20891
|
+
summary: "",
|
|
20892
|
+
resultText: "",
|
|
20893
|
+
createdAt: now,
|
|
20894
|
+
updatedAt: now,
|
|
20895
|
+
eventSeq: 1,
|
|
20896
|
+
events: [{ seq: 1, at: now, type: "created", status: "running", message: "Task created" }],
|
|
20897
|
+
...input.chatKey ? { chatKey: input.chatKey } : {},
|
|
20898
|
+
...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
|
|
20899
|
+
...input.accountId ? { accountId: input.accountId } : {},
|
|
20900
|
+
...input.parallel ? { ephemeralWorkerSession: true } : {}
|
|
20901
|
+
};
|
|
20902
|
+
let previousGroup;
|
|
20903
|
+
if (normalizedGroupId) {
|
|
20904
|
+
const group = this.ensureGroups(state)[normalizedGroupId];
|
|
20905
|
+
previousGroup = { ...group };
|
|
20906
|
+
group.updatedAt = now;
|
|
20907
|
+
group.coordinatorInjectedAt = undefined;
|
|
20908
|
+
group.injectionPending = undefined;
|
|
20909
|
+
group.injectionAppliedAt = undefined;
|
|
20910
|
+
group.lastInjectionError = undefined;
|
|
20911
|
+
}
|
|
20912
|
+
const previousBinding = state.orchestration.workerBindings[ensuredWorkerSession];
|
|
20913
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, ensuredWorkerSession);
|
|
20914
|
+
this.assertWorkerSessionAvailable(state, ensuredWorkerSession, undefined, { allowCurrentReservation: true });
|
|
20915
|
+
state.orchestration.tasks[taskId] = task;
|
|
20916
|
+
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
20917
|
+
sourceHandle: ensuredWorkerSession,
|
|
20918
|
+
coordinatorSession: input.coordinatorSession,
|
|
20919
|
+
workspace: input.workspace,
|
|
20920
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
20921
|
+
targetAgent: input.targetAgent,
|
|
20922
|
+
role,
|
|
20923
|
+
...input.parallel ? { ephemeral: true } : {}
|
|
20924
|
+
};
|
|
20925
|
+
await this.deps.saveState(state);
|
|
20926
|
+
return {
|
|
20927
|
+
task: { ...task },
|
|
20928
|
+
previousBinding,
|
|
20929
|
+
previousGroup,
|
|
20930
|
+
normalizedGroupId
|
|
20931
|
+
};
|
|
20932
|
+
});
|
|
20933
|
+
} catch (error2) {
|
|
20934
|
+
await releaseWorkerReservation();
|
|
20935
|
+
throw error2;
|
|
20936
|
+
}
|
|
20052
20937
|
await releaseWorkerReservation();
|
|
20053
|
-
|
|
20938
|
+
} finally {
|
|
20939
|
+
releasePendingParallelStart?.();
|
|
20054
20940
|
}
|
|
20055
|
-
await releaseWorkerReservation();
|
|
20056
20941
|
try {
|
|
20057
20942
|
await this.deps.dispatchWorkerTask({
|
|
20058
20943
|
taskId,
|
|
@@ -20102,6 +20987,7 @@ class OrchestrationService {
|
|
|
20102
20987
|
return { sourceContext, targetLocation, role, normalizedGroupId };
|
|
20103
20988
|
});
|
|
20104
20989
|
const autoRun = preflight.sourceContext.sourceKind === "coordinator";
|
|
20990
|
+
const taskId = this.deps.createId();
|
|
20105
20991
|
const workerSessionName = await this.resolveWorkerSession({
|
|
20106
20992
|
sourceHandle: input.sourceHandle,
|
|
20107
20993
|
sourceKind: preflight.sourceContext.sourceKind,
|
|
@@ -20110,18 +20996,18 @@ class OrchestrationService {
|
|
|
20110
20996
|
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
20111
20997
|
targetAgent: input.targetAgent,
|
|
20112
20998
|
task: input.task,
|
|
20113
|
-
...preflight.role ? { role: preflight.role } : {}
|
|
20999
|
+
...preflight.role ? { role: preflight.role } : {},
|
|
21000
|
+
...input.parallel ? { parallel: true } : {}
|
|
20114
21001
|
});
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
try {
|
|
20118
|
-
prepared = await this.mutate(async () => {
|
|
21002
|
+
if (input.parallel && autoRun) {
|
|
21003
|
+
const queuedResult = await this.mutate(async () => {
|
|
20119
21004
|
const state = await this.deps.loadState();
|
|
20120
|
-
this.
|
|
21005
|
+
if (this.canStartParallelTask(state, input.targetAgent)) {
|
|
21006
|
+
this.pendingParallelStarts.set(input.targetAgent, (this.pendingParallelStarts.get(input.targetAgent) ?? 0) + 1);
|
|
21007
|
+
return null;
|
|
21008
|
+
}
|
|
20121
21009
|
const now = this.deps.now().toISOString();
|
|
20122
|
-
const
|
|
20123
|
-
const status = autoRun ? "running" : "needs_confirmation";
|
|
20124
|
-
const task = {
|
|
21010
|
+
const queuedTask = {
|
|
20125
21011
|
taskId,
|
|
20126
21012
|
sourceHandle: input.sourceHandle,
|
|
20127
21013
|
sourceKind: preflight.sourceContext.sourceKind,
|
|
@@ -20133,49 +21019,101 @@ class OrchestrationService {
|
|
|
20133
21019
|
...preflight.role ? { role: preflight.role } : {},
|
|
20134
21020
|
...preflight.normalizedGroupId ? { groupId: preflight.normalizedGroupId } : {},
|
|
20135
21021
|
task: input.task,
|
|
20136
|
-
status,
|
|
21022
|
+
status: "queued",
|
|
21023
|
+
ephemeralWorkerSession: true,
|
|
20137
21024
|
summary: "",
|
|
20138
21025
|
resultText: "",
|
|
20139
21026
|
createdAt: now,
|
|
20140
21027
|
updatedAt: now,
|
|
20141
21028
|
eventSeq: 1,
|
|
20142
|
-
events: [{ seq: 1, at: now, type: "created", status, message: "Task
|
|
21029
|
+
events: [{ seq: 1, at: now, type: "created", status: "queued", message: "Task queued at parallel capacity" }]
|
|
20143
21030
|
};
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
20156
|
-
|
|
20157
|
-
|
|
20158
|
-
|
|
20159
|
-
|
|
21031
|
+
state.orchestration.tasks[taskId] = queuedTask;
|
|
21032
|
+
await this.deps.saveState(state);
|
|
21033
|
+
return { taskId, status: "queued", workerSession: workerSessionName };
|
|
21034
|
+
});
|
|
21035
|
+
if (queuedResult) {
|
|
21036
|
+
this.logEvent("orchestration.task.queued", "parallel task queued at capacity", { taskId, targetAgent: input.targetAgent });
|
|
21037
|
+
return queuedResult;
|
|
21038
|
+
}
|
|
21039
|
+
}
|
|
21040
|
+
const releasePendingParallelStart = input.parallel && autoRun ? () => {
|
|
21041
|
+
const count = this.pendingParallelStarts.get(input.targetAgent) ?? 0;
|
|
21042
|
+
if (count <= 1) {
|
|
21043
|
+
this.pendingParallelStarts.delete(input.targetAgent);
|
|
21044
|
+
} else {
|
|
21045
|
+
this.pendingParallelStarts.set(input.targetAgent, count - 1);
|
|
21046
|
+
}
|
|
21047
|
+
} : undefined;
|
|
21048
|
+
let prepared;
|
|
21049
|
+
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSessionName);
|
|
21050
|
+
try {
|
|
21051
|
+
try {
|
|
21052
|
+
prepared = await this.mutate(async () => {
|
|
21053
|
+
const state = await this.deps.loadState();
|
|
21054
|
+
this.assertRpcRequestAllowed(state, preflight.sourceContext.sourceKind, preflight.sourceContext.coordinatorSession, input.targetAgent, preflight.role);
|
|
21055
|
+
const now = this.deps.now().toISOString();
|
|
21056
|
+
const status = autoRun ? "running" : "needs_confirmation";
|
|
21057
|
+
const task = {
|
|
21058
|
+
taskId,
|
|
21059
|
+
sourceHandle: input.sourceHandle,
|
|
21060
|
+
sourceKind: preflight.sourceContext.sourceKind,
|
|
20160
21061
|
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
21062
|
+
workerSession: workerSessionName,
|
|
20161
21063
|
workspace: preflight.targetLocation.workspace,
|
|
20162
21064
|
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
20163
21065
|
targetAgent: input.targetAgent,
|
|
20164
|
-
role: preflight.role
|
|
21066
|
+
...preflight.role ? { role: preflight.role } : {},
|
|
21067
|
+
...preflight.normalizedGroupId ? { groupId: preflight.normalizedGroupId } : {},
|
|
21068
|
+
task: input.task,
|
|
21069
|
+
status,
|
|
21070
|
+
summary: "",
|
|
21071
|
+
resultText: "",
|
|
21072
|
+
createdAt: now,
|
|
21073
|
+
updatedAt: now,
|
|
21074
|
+
eventSeq: 1,
|
|
21075
|
+
events: [{ seq: 1, at: now, type: "created", status, message: "Task created" }],
|
|
21076
|
+
...input.parallel ? { ephemeralWorkerSession: true } : {}
|
|
20165
21077
|
};
|
|
20166
|
-
|
|
20167
|
-
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
|
|
21078
|
+
if (preflight.normalizedGroupId) {
|
|
21079
|
+
const group = this.ensureGroups(state)[preflight.normalizedGroupId];
|
|
21080
|
+
group.updatedAt = now;
|
|
21081
|
+
group.coordinatorInjectedAt = undefined;
|
|
21082
|
+
group.injectionPending = undefined;
|
|
21083
|
+
group.injectionAppliedAt = undefined;
|
|
21084
|
+
group.lastInjectionError = undefined;
|
|
21085
|
+
}
|
|
21086
|
+
let previousBinding;
|
|
21087
|
+
if (autoRun) {
|
|
21088
|
+
previousBinding = state.orchestration.workerBindings[workerSessionName];
|
|
21089
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSessionName);
|
|
21090
|
+
this.assertWorkerSessionAvailable(state, workerSessionName, undefined, { allowCurrentReservation: true });
|
|
21091
|
+
state.orchestration.tasks[taskId] = task;
|
|
21092
|
+
state.orchestration.workerBindings[workerSessionName] = {
|
|
21093
|
+
sourceHandle: workerSessionName,
|
|
21094
|
+
coordinatorSession: preflight.sourceContext.coordinatorSession,
|
|
21095
|
+
workspace: preflight.targetLocation.workspace,
|
|
21096
|
+
...preflight.targetLocation.cwd ? { cwd: preflight.targetLocation.cwd } : {},
|
|
21097
|
+
targetAgent: input.targetAgent,
|
|
21098
|
+
role: preflight.role,
|
|
21099
|
+
...input.parallel ? { ephemeral: true } : {}
|
|
21100
|
+
};
|
|
21101
|
+
} else {
|
|
21102
|
+
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSessionName);
|
|
21103
|
+
this.assertWorkerSessionAvailable(state, workerSessionName, undefined, { allowCurrentReservation: true });
|
|
21104
|
+
state.orchestration.tasks[taskId] = task;
|
|
21105
|
+
}
|
|
21106
|
+
await this.deps.saveState(state);
|
|
21107
|
+
return { task: { ...task }, status, previousBinding, normalizedGroupId: preflight.normalizedGroupId };
|
|
21108
|
+
});
|
|
21109
|
+
} catch (error2) {
|
|
21110
|
+
await releaseWorkerReservation();
|
|
21111
|
+
throw error2;
|
|
21112
|
+
}
|
|
20175
21113
|
await releaseWorkerReservation();
|
|
20176
|
-
|
|
21114
|
+
} finally {
|
|
21115
|
+
releasePendingParallelStart?.();
|
|
20177
21116
|
}
|
|
20178
|
-
await releaseWorkerReservation();
|
|
20179
21117
|
if (autoRun) {
|
|
20180
21118
|
this.runAutoRunRpcWorkerTask({
|
|
20181
21119
|
task: prepared.task,
|
|
@@ -21144,6 +22082,16 @@ class OrchestrationService {
|
|
|
21144
22082
|
await this.recordOpenQuestionWakeError(prepared.task.taskId, prepared.replacementQuestionId, error2 instanceof Error ? error2.message : String(error2));
|
|
21145
22083
|
}
|
|
21146
22084
|
}
|
|
22085
|
+
if (input.decision === "accept") {
|
|
22086
|
+
try {
|
|
22087
|
+
await this.reconcileParallelSlots();
|
|
22088
|
+
} catch (error2) {
|
|
22089
|
+
this.logEvent("orchestration.parallel.reconcile_failed", "reconcile failed after contested result accepted", {
|
|
22090
|
+
taskId: prepared.task.taskId,
|
|
22091
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
22092
|
+
});
|
|
22093
|
+
}
|
|
22094
|
+
}
|
|
21147
22095
|
return prepared.task;
|
|
21148
22096
|
}
|
|
21149
22097
|
async listTasks(filter) {
|
|
@@ -21610,6 +22558,16 @@ class OrchestrationService {
|
|
|
21610
22558
|
if (prepared.closedPackageId) {
|
|
21611
22559
|
await this.handoffQueuedQuestions(prepared.task.coordinatorSession, prepared.closedPackageId);
|
|
21612
22560
|
}
|
|
22561
|
+
if (!prepared.shouldPropagate && this.isTerminalStatus(prepared.task.status)) {
|
|
22562
|
+
try {
|
|
22563
|
+
await this.reconcileParallelSlots();
|
|
22564
|
+
} catch (error2) {
|
|
22565
|
+
this.logEvent("orchestration.parallel.reconcile_failed", "reconcile failed after non-running cancel", {
|
|
22566
|
+
taskId: prepared.task.taskId,
|
|
22567
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
22568
|
+
});
|
|
22569
|
+
}
|
|
22570
|
+
}
|
|
21613
22571
|
return prepared.task;
|
|
21614
22572
|
}
|
|
21615
22573
|
async completeTaskCancellation(taskId) {
|
|
@@ -21672,6 +22630,14 @@ class OrchestrationService {
|
|
|
21672
22630
|
return prepared.task;
|
|
21673
22631
|
}
|
|
21674
22632
|
this.logEvent("orchestration.task.cancel_completed", "task cancellation completed", this.taskContext(prepared.task));
|
|
22633
|
+
try {
|
|
22634
|
+
await this.reconcileParallelSlots();
|
|
22635
|
+
} catch (error2) {
|
|
22636
|
+
this.logEvent("orchestration.parallel.reconcile_failed", "reconcile failed after cancel completion", {
|
|
22637
|
+
taskId: prepared.task.taskId,
|
|
22638
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
22639
|
+
});
|
|
22640
|
+
}
|
|
21675
22641
|
return prepared.task;
|
|
21676
22642
|
}
|
|
21677
22643
|
async failTaskCancellation(taskId, errorMessage) {
|
|
@@ -21716,6 +22682,34 @@ class OrchestrationService {
|
|
|
21716
22682
|
task: currentTask.task,
|
|
21717
22683
|
...currentTask.role ? { role: currentTask.role } : {}
|
|
21718
22684
|
});
|
|
22685
|
+
if (currentTask.ephemeralWorkerSession === true) {
|
|
22686
|
+
const queuedResult = await this.mutate(async () => {
|
|
22687
|
+
const state = await this.deps.loadState();
|
|
22688
|
+
const task = state.orchestration.tasks[input.taskId];
|
|
22689
|
+
if (!task) {
|
|
22690
|
+
throw new Error(`task "${input.taskId}" does not exist`);
|
|
22691
|
+
}
|
|
22692
|
+
this.assertCoordinatorOwnership(task, input.coordinatorSession);
|
|
22693
|
+
this.assertNeedsConfirmation(task);
|
|
22694
|
+
if (this.canStartParallelTask(state, task.targetAgent)) {
|
|
22695
|
+
return null;
|
|
22696
|
+
}
|
|
22697
|
+
const now = this.deps.now().toISOString();
|
|
22698
|
+
task.workerSession = workerSession;
|
|
22699
|
+
task.status = "queued";
|
|
22700
|
+
task.updatedAt = now;
|
|
22701
|
+
this.appendTaskEvent(task, now, "status_changed", {
|
|
22702
|
+
status: "queued",
|
|
22703
|
+
message: "Task queued at parallel capacity"
|
|
22704
|
+
});
|
|
22705
|
+
await this.deps.saveState(state);
|
|
22706
|
+
return { ...task };
|
|
22707
|
+
});
|
|
22708
|
+
if (queuedResult) {
|
|
22709
|
+
this.logEvent("orchestration.task.queued", "parallel task queued at capacity on approve", { taskId: input.taskId, targetAgent: currentTask.targetAgent });
|
|
22710
|
+
return queuedResult;
|
|
22711
|
+
}
|
|
22712
|
+
}
|
|
21719
22713
|
const releaseWorkerReservation = await this.reserveProposedWorkerSession(workerSession, input.taskId);
|
|
21720
22714
|
let ensuredWorkerSession = workerSession;
|
|
21721
22715
|
let prepared;
|
|
@@ -21757,7 +22751,8 @@ class OrchestrationService {
|
|
|
21757
22751
|
workspace: task.workspace,
|
|
21758
22752
|
...task.cwd ? { cwd: task.cwd } : {},
|
|
21759
22753
|
targetAgent: task.targetAgent,
|
|
21760
|
-
role: task.role
|
|
22754
|
+
role: task.role,
|
|
22755
|
+
...task.ephemeralWorkerSession ? { ephemeral: true } : {}
|
|
21761
22756
|
};
|
|
21762
22757
|
await this.deps.saveState(state);
|
|
21763
22758
|
return {
|
|
@@ -21811,6 +22806,10 @@ class OrchestrationService {
|
|
|
21811
22806
|
}
|
|
21812
22807
|
async resolveWorkerSession(input) {
|
|
21813
22808
|
const role = this.normalizeRole(input.role);
|
|
22809
|
+
const baseName = [input.workspace, input.cwd ? this.cwdWorkerSessionPart(input.cwd) : undefined, input.targetAgent, role, input.coordinatorSession].filter((part) => typeof part === "string" && part.trim().length > 0).map((part) => part.trim()).join(":");
|
|
22810
|
+
if (input.parallel) {
|
|
22811
|
+
return `${baseName}:p-${this.deps.createId()}`;
|
|
22812
|
+
}
|
|
21814
22813
|
const reusable = await this.deps.findReusableWorkerSession?.({
|
|
21815
22814
|
sourceHandle: input.sourceHandle,
|
|
21816
22815
|
sourceKind: input.sourceKind,
|
|
@@ -21823,7 +22822,7 @@ class OrchestrationService {
|
|
|
21823
22822
|
if (reusable && reusable.trim().length > 0) {
|
|
21824
22823
|
return reusable.trim();
|
|
21825
22824
|
}
|
|
21826
|
-
return
|
|
22825
|
+
return baseName;
|
|
21827
22826
|
}
|
|
21828
22827
|
async reserveProposedWorkerSession(workerSession, excludingTaskId) {
|
|
21829
22828
|
await this.mutate(async () => {
|
|
@@ -21981,7 +22980,7 @@ class OrchestrationService {
|
|
|
21981
22980
|
if (role && policy.allowedAgentRequestRoles.length > 0 && !policy.allowedAgentRequestRoles.includes(role)) {
|
|
21982
22981
|
throw new Error(`role "${role}" is not allowed for agent-requested delegation`);
|
|
21983
22982
|
}
|
|
21984
|
-
const outstandingRequests = Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession === coordinatorSession && task.sourceKind !== "human" && (task.status === "needs_confirmation" || task.status === "running"));
|
|
22983
|
+
const outstandingRequests = Object.values(state.orchestration.tasks).filter((task) => task.coordinatorSession === coordinatorSession && task.sourceKind !== "human" && (task.status === "needs_confirmation" || task.status === "running" || task.status === "queued"));
|
|
21985
22984
|
if (outstandingRequests.length >= policy.maxPendingAgentRequestsPerCoordinator) {
|
|
21986
22985
|
throw new Error("agent-requested delegation quota exceeded for this coordinator");
|
|
21987
22986
|
}
|
|
@@ -22146,6 +23145,137 @@ class OrchestrationService {
|
|
|
22146
23145
|
hasActiveTaskWorkerSession(state, workerSession, excludingTaskId) {
|
|
22147
23146
|
return Object.values(state.orchestration.tasks).some((task) => task.taskId !== excludingTaskId && task.workerSession === workerSession && (!this.isTerminalStatus(task.status) || task.reviewPending !== undefined));
|
|
22148
23147
|
}
|
|
23148
|
+
countActiveParallelSlots(state, targetAgent) {
|
|
23149
|
+
const persisted = Object.values(state.orchestration.tasks).filter((task) => task.ephemeralWorkerSession === true && task.targetAgent === targetAgent && (task.status === "running" || task.status === "blocked" || task.status === "waiting_for_human")).length;
|
|
23150
|
+
const pending = this.pendingParallelStarts.get(targetAgent) ?? 0;
|
|
23151
|
+
return persisted + pending;
|
|
23152
|
+
}
|
|
23153
|
+
canStartParallelTask(state, targetAgent) {
|
|
23154
|
+
const cap = this.deps.config.orchestration.maxParallelTasksPerAgent;
|
|
23155
|
+
return this.countActiveParallelSlots(state, targetAgent) < cap;
|
|
23156
|
+
}
|
|
23157
|
+
async reconcileParallelSlots() {
|
|
23158
|
+
const toClose = await this.mutate(async () => {
|
|
23159
|
+
const state = await this.deps.loadState();
|
|
23160
|
+
const collected = [];
|
|
23161
|
+
for (const task of Object.values(state.orchestration.tasks)) {
|
|
23162
|
+
if (task.ephemeralWorkerSession === true && task.ephemeralWorkerSessionClosed !== true && task.workerSession && task.reviewPending === undefined && this.isTerminalStatus(task.status)) {
|
|
23163
|
+
task.ephemeralWorkerSessionClosed = true;
|
|
23164
|
+
if (state.orchestration.workerBindings[task.workerSession] !== undefined) {
|
|
23165
|
+
delete state.orchestration.workerBindings[task.workerSession];
|
|
23166
|
+
collected.push({
|
|
23167
|
+
workerSession: task.workerSession,
|
|
23168
|
+
coordinatorSession: task.coordinatorSession,
|
|
23169
|
+
workspace: task.workspace,
|
|
23170
|
+
...task.cwd ? { cwd: task.cwd } : {},
|
|
23171
|
+
targetAgent: task.targetAgent,
|
|
23172
|
+
...task.role ? { role: task.role } : {}
|
|
23173
|
+
});
|
|
23174
|
+
}
|
|
23175
|
+
}
|
|
23176
|
+
}
|
|
23177
|
+
if (collected.length > 0) {
|
|
23178
|
+
await this.deps.saveState(state);
|
|
23179
|
+
}
|
|
23180
|
+
return collected;
|
|
23181
|
+
});
|
|
23182
|
+
for (const req of toClose) {
|
|
23183
|
+
try {
|
|
23184
|
+
await this.deps.closeWorkerSession?.(req);
|
|
23185
|
+
} catch (error2) {
|
|
23186
|
+
this.logEvent("orchestration.parallel.close_failed", "failed to close ephemeral worker session", {
|
|
23187
|
+
workerSession: req.workerSession,
|
|
23188
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
23189
|
+
});
|
|
23190
|
+
}
|
|
23191
|
+
}
|
|
23192
|
+
for (;; ) {
|
|
23193
|
+
const next = await this.mutate(async () => {
|
|
23194
|
+
const state = await this.deps.loadState();
|
|
23195
|
+
const queued = Object.values(state.orchestration.tasks).filter((t) => t.status === "queued" && t.ephemeralWorkerSession === true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
23196
|
+
for (const task of queued) {
|
|
23197
|
+
if (!this.canStartParallelTask(state, task.targetAgent)) {
|
|
23198
|
+
continue;
|
|
23199
|
+
}
|
|
23200
|
+
task.status = "running";
|
|
23201
|
+
task.updatedAt = this.deps.now().toISOString();
|
|
23202
|
+
state.orchestration.workerBindings[task.workerSession] = {
|
|
23203
|
+
sourceHandle: task.workerSession,
|
|
23204
|
+
coordinatorSession: task.coordinatorSession,
|
|
23205
|
+
workspace: task.workspace,
|
|
23206
|
+
...task.cwd ? { cwd: task.cwd } : {},
|
|
23207
|
+
targetAgent: task.targetAgent,
|
|
23208
|
+
...task.role ? { role: task.role } : {},
|
|
23209
|
+
ephemeral: true
|
|
23210
|
+
};
|
|
23211
|
+
await this.deps.saveState(state);
|
|
23212
|
+
return { ...task };
|
|
23213
|
+
}
|
|
23214
|
+
return null;
|
|
23215
|
+
});
|
|
23216
|
+
if (!next) {
|
|
23217
|
+
break;
|
|
23218
|
+
}
|
|
23219
|
+
try {
|
|
23220
|
+
await this.ensureReservedWorkerSession({
|
|
23221
|
+
workerSession: next.workerSession,
|
|
23222
|
+
sourceHandle: next.sourceHandle,
|
|
23223
|
+
sourceKind: next.sourceKind,
|
|
23224
|
+
coordinatorSession: next.coordinatorSession,
|
|
23225
|
+
workspace: next.workspace,
|
|
23226
|
+
...next.cwd ? { cwd: next.cwd } : {},
|
|
23227
|
+
targetAgent: next.targetAgent,
|
|
23228
|
+
...next.role ? { role: next.role } : {}
|
|
23229
|
+
});
|
|
23230
|
+
await this.deps.dispatchWorkerTask({
|
|
23231
|
+
taskId: next.taskId,
|
|
23232
|
+
workerSession: next.workerSession,
|
|
23233
|
+
coordinatorSession: next.coordinatorSession,
|
|
23234
|
+
workspace: next.workspace,
|
|
23235
|
+
...next.cwd ? { cwd: next.cwd } : {},
|
|
23236
|
+
targetAgent: next.targetAgent,
|
|
23237
|
+
...next.role ? { role: next.role } : {},
|
|
23238
|
+
task: next.task
|
|
23239
|
+
});
|
|
23240
|
+
} catch (error2) {
|
|
23241
|
+
await this.mutate(async () => {
|
|
23242
|
+
const state = await this.deps.loadState();
|
|
23243
|
+
const task = state.orchestration.tasks[next.taskId];
|
|
23244
|
+
if (task && task.status === "running") {
|
|
23245
|
+
task.status = "queued";
|
|
23246
|
+
task.updatedAt = this.deps.now().toISOString();
|
|
23247
|
+
delete state.orchestration.workerBindings[next.workerSession];
|
|
23248
|
+
this.appendTaskEvent(task, task.updatedAt, "status_changed", {
|
|
23249
|
+
status: "queued",
|
|
23250
|
+
message: "Task re-queued after drain failure"
|
|
23251
|
+
});
|
|
23252
|
+
await this.deps.saveState(state);
|
|
23253
|
+
}
|
|
23254
|
+
});
|
|
23255
|
+
this.logEvent("orchestration.parallel.drain_failed", "failed to drain queued parallel task", {
|
|
23256
|
+
taskId: next.taskId,
|
|
23257
|
+
workerSession: next.workerSession,
|
|
23258
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
23259
|
+
});
|
|
23260
|
+
break;
|
|
23261
|
+
}
|
|
23262
|
+
await this.mutate(async () => {
|
|
23263
|
+
const state = await this.deps.loadState();
|
|
23264
|
+
const task = state.orchestration.tasks[next.taskId];
|
|
23265
|
+
if (task && task.status === "running") {
|
|
23266
|
+
this.appendTaskEvent(task, task.updatedAt, "status_changed", {
|
|
23267
|
+
status: "running",
|
|
23268
|
+
message: "Task drained from parallel queue"
|
|
23269
|
+
});
|
|
23270
|
+
await this.deps.saveState(state);
|
|
23271
|
+
}
|
|
23272
|
+
});
|
|
23273
|
+
this.logEvent("orchestration.task.drained", "parallel task drained from queue", {
|
|
23274
|
+
taskId: next.taskId,
|
|
23275
|
+
targetAgent: next.targetAgent
|
|
23276
|
+
});
|
|
23277
|
+
}
|
|
23278
|
+
}
|
|
22149
23279
|
async assertProposedWorkerSessionDoesNotConflictExternalCoordinator(workerSession) {
|
|
22150
23280
|
const state = await this.deps.loadState();
|
|
22151
23281
|
this.assertWorkerSessionDoesNotConflictExternalCoordinator(state, workerSession);
|
|
@@ -23156,6 +24286,13 @@ async function runConsole(paths, deps) {
|
|
|
23156
24286
|
trigger: "startup"
|
|
23157
24287
|
});
|
|
23158
24288
|
} catch {}
|
|
24289
|
+
try {
|
|
24290
|
+
await runtime.orchestration.service.reconcileParallelSlots();
|
|
24291
|
+
} catch (reconcileError) {
|
|
24292
|
+
await runtime.logger.error("orchestration.parallel.reconcile_failed", "failed to reconcile parallel slots at startup", {
|
|
24293
|
+
message: reconcileError instanceof Error ? reconcileError.message : String(reconcileError)
|
|
24294
|
+
});
|
|
24295
|
+
}
|
|
23159
24296
|
consumerLock = deps.consumerLock ?? deps.consumerLockFactory?.(runtime);
|
|
23160
24297
|
if (consumerLock) {
|
|
23161
24298
|
const lockMeta = {
|
|
@@ -23333,6 +24470,10 @@ function encodeBridgePromptToolEvent(event) {
|
|
|
23333
24470
|
return `${JSON.stringify(event)}
|
|
23334
24471
|
`;
|
|
23335
24472
|
}
|
|
24473
|
+
function encodeBridgePromptThoughtEvent(event) {
|
|
24474
|
+
return `${JSON.stringify(event)}
|
|
24475
|
+
`;
|
|
24476
|
+
}
|
|
23336
24477
|
function encodeBridgeSessionProgressEvent(event) {
|
|
23337
24478
|
return `${JSON.stringify(event)}
|
|
23338
24479
|
`;
|
|
@@ -23405,6 +24546,11 @@ class AcpxBridgeClient {
|
|
|
23405
24546
|
type: "prompt.tool_event",
|
|
23406
24547
|
event: message.toolEvent
|
|
23407
24548
|
});
|
|
24549
|
+
} else if (message.event === "prompt.thought") {
|
|
24550
|
+
pending.onEvent?.({
|
|
24551
|
+
type: "prompt.thought",
|
|
24552
|
+
text: message.text
|
|
24553
|
+
});
|
|
23408
24554
|
} else if (message.event === "session.progress") {
|
|
23409
24555
|
pending.onEvent?.({
|
|
23410
24556
|
type: "session.progress",
|
|
@@ -23754,6 +24900,12 @@ class AcpxBridgeTransport {
|
|
|
23754
24900
|
}
|
|
23755
24901
|
} : undefined);
|
|
23756
24902
|
}
|
|
24903
|
+
async tailSessionHistory(session, lines) {
|
|
24904
|
+
return await this.client.request("tailSessionHistory", {
|
|
24905
|
+
...this.toParams(session),
|
|
24906
|
+
lines
|
|
24907
|
+
});
|
|
24908
|
+
}
|
|
23757
24909
|
async prompt(session, text, reply, replyContext, options) {
|
|
23758
24910
|
const sink = reply ? createQuotaGatedReplySink({
|
|
23759
24911
|
reply,
|
|
@@ -23763,6 +24915,8 @@ class AcpxBridgeTransport {
|
|
|
23763
24915
|
let segmentChain = Promise.resolve();
|
|
23764
24916
|
let toolEventError;
|
|
23765
24917
|
let toolEventChain = Promise.resolve();
|
|
24918
|
+
let thoughtError;
|
|
24919
|
+
let thoughtChain = Promise.resolve();
|
|
23766
24920
|
let toolEventMode = resolveToolEventMode(options);
|
|
23767
24921
|
if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
|
|
23768
24922
|
toolEventMode = "text";
|
|
@@ -23795,9 +24949,20 @@ class AcpxBridgeTransport {
|
|
|
23795
24949
|
}
|
|
23796
24950
|
return;
|
|
23797
24951
|
}
|
|
24952
|
+
if (event.type === "prompt.thought") {
|
|
24953
|
+
const onThought = options?.onThought;
|
|
24954
|
+
if (onThought) {
|
|
24955
|
+
const thoughtText = event.text;
|
|
24956
|
+
thoughtChain = thoughtChain.then(() => onThought(thoughtText)).catch((error2) => {
|
|
24957
|
+
thoughtError ??= error2;
|
|
24958
|
+
});
|
|
24959
|
+
}
|
|
24960
|
+
return;
|
|
24961
|
+
}
|
|
23798
24962
|
});
|
|
23799
24963
|
await segmentChain;
|
|
23800
24964
|
await toolEventChain;
|
|
24965
|
+
await thoughtChain;
|
|
23801
24966
|
if (sink) {
|
|
23802
24967
|
const { overflowCount } = sink.finalize();
|
|
23803
24968
|
await sink.drain({ timeoutMs: 30000 });
|
|
@@ -23812,6 +24977,9 @@ class AcpxBridgeTransport {
|
|
|
23812
24977
|
if (toolEventError) {
|
|
23813
24978
|
throw toolEventError;
|
|
23814
24979
|
}
|
|
24980
|
+
if (thoughtError) {
|
|
24981
|
+
throw thoughtError;
|
|
24982
|
+
}
|
|
23815
24983
|
return { text: summary ? `${summary}
|
|
23816
24984
|
|
|
23817
24985
|
${result.text}` : "" };
|
|
@@ -23822,6 +24990,9 @@ ${result.text}` : "" };
|
|
|
23822
24990
|
if (toolEventError) {
|
|
23823
24991
|
throw toolEventError;
|
|
23824
24992
|
}
|
|
24993
|
+
if (thoughtError) {
|
|
24994
|
+
throw thoughtError;
|
|
24995
|
+
}
|
|
23825
24996
|
return result;
|
|
23826
24997
|
}
|
|
23827
24998
|
async setMode(session, modeId) {
|
|
@@ -23880,7 +25051,7 @@ var init_spawn_command = __esm(() => {
|
|
|
23880
25051
|
// src/transport/prompt-media.ts
|
|
23881
25052
|
import { mkdtemp, open as open3, rm as rm8, writeFile as writeFile8 } from "node:fs/promises";
|
|
23882
25053
|
import { tmpdir as defaultTmpdir } from "node:os";
|
|
23883
|
-
import
|
|
25054
|
+
import path14 from "node:path";
|
|
23884
25055
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
23885
25056
|
async function createStructuredPromptFile(text, media, deps = defaultStructuredPromptFileDeps) {
|
|
23886
25057
|
const mediaList = normalizePromptMedia(media);
|
|
@@ -23914,7 +25085,7 @@ async function createStructuredPromptFile(text, media, deps = defaultStructuredP
|
|
|
23914
25085
|
type: "resource",
|
|
23915
25086
|
resource: {
|
|
23916
25087
|
uri: pathToFileURL2(item.filePath).toString(),
|
|
23917
|
-
text: `${item.fileName ??
|
|
25088
|
+
text: `${item.fileName ?? path14.basename(item.filePath)} ${item.mimeType} ${item.type}`
|
|
23918
25089
|
}
|
|
23919
25090
|
});
|
|
23920
25091
|
}
|
|
@@ -23928,7 +25099,7 @@ function normalizePromptMedia(media) {
|
|
|
23928
25099
|
function buildAttachmentSummary(items) {
|
|
23929
25100
|
const lines = ["Attachments available as local files:"];
|
|
23930
25101
|
for (const [index, item] of items.entries()) {
|
|
23931
|
-
lines.push(`${index + 1}. ${item.type} ${item.fileName ??
|
|
25102
|
+
lines.push(`${index + 1}. ${item.type} ${item.fileName ?? path14.basename(item.filePath)} ${item.mimeType} ${item.filePath}`);
|
|
23932
25103
|
}
|
|
23933
25104
|
return lines.join(`
|
|
23934
25105
|
`);
|
|
@@ -23936,8 +25107,8 @@ function buildAttachmentSummary(items) {
|
|
|
23936
25107
|
async function writeStructuredPromptBlocks(blocks, deps) {
|
|
23937
25108
|
let dir = "";
|
|
23938
25109
|
try {
|
|
23939
|
-
dir = await deps.mkdtemp(
|
|
23940
|
-
const filePath =
|
|
25110
|
+
dir = await deps.mkdtemp(path14.join(deps.tmpdir(), "weacpx-acp-prompt-"));
|
|
25111
|
+
const filePath = path14.join(dir, "prompt.json");
|
|
23941
25112
|
await deps.writeFile(filePath, JSON.stringify(blocks), "utf8");
|
|
23942
25113
|
return { filePath, cleanup: async () => deps.rm(dir, { recursive: true, force: true }) };
|
|
23943
25114
|
} catch (error2) {
|
|
@@ -24026,6 +25197,7 @@ var init_tool_kind_emoji = __esm(() => {
|
|
|
24026
25197
|
function createStreamingPromptState(formatToolCalls = false, options) {
|
|
24027
25198
|
let toolEventMode;
|
|
24028
25199
|
let onToolEvent;
|
|
25200
|
+
let onThought;
|
|
24029
25201
|
if (options === undefined) {
|
|
24030
25202
|
toolEventMode = "text";
|
|
24031
25203
|
onToolEvent = undefined;
|
|
@@ -24034,6 +25206,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
24034
25206
|
toolEventMode = "structured";
|
|
24035
25207
|
} else {
|
|
24036
25208
|
onToolEvent = options.onToolEvent;
|
|
25209
|
+
onThought = options.onThought;
|
|
24037
25210
|
toolEventMode = resolveToolEventMode({
|
|
24038
25211
|
toolEventMode: options.mode,
|
|
24039
25212
|
onToolEvent
|
|
@@ -24048,6 +25221,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
24048
25221
|
emittedToolCallIds: new Set,
|
|
24049
25222
|
toolEventMode,
|
|
24050
25223
|
onToolEvent,
|
|
25224
|
+
onThought,
|
|
24051
25225
|
finalize() {
|
|
24052
25226
|
if (this.pendingLine.trim().length > 0) {
|
|
24053
25227
|
parseStreamingChunks(this, this.pendingLine);
|
|
@@ -24106,6 +25280,14 @@ function parseStreamingChunks(state, line) {
|
|
|
24106
25280
|
}
|
|
24107
25281
|
return;
|
|
24108
25282
|
}
|
|
25283
|
+
const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
25284
|
+
if (isThoughtChunk) {
|
|
25285
|
+
const chunk2 = update.content.text;
|
|
25286
|
+
if (chunk2.length > 0) {
|
|
25287
|
+
state.onThought?.(chunk2);
|
|
25288
|
+
}
|
|
25289
|
+
return;
|
|
25290
|
+
}
|
|
24109
25291
|
const isMessageChunk = update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
24110
25292
|
if (!isMessageChunk)
|
|
24111
25293
|
return;
|
|
@@ -24133,7 +25315,7 @@ function formatToolCallEvent(update, sessionUpdate) {
|
|
|
24133
25315
|
if (title.length === 0)
|
|
24134
25316
|
return null;
|
|
24135
25317
|
const emoji2 = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
|
|
24136
|
-
const inputSummary = summarizeToolInput(update.rawInput, title);
|
|
25318
|
+
const inputSummary = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
|
|
24137
25319
|
const status = readString(update, "status");
|
|
24138
25320
|
if (!inputSummary && status === "pending")
|
|
24139
25321
|
return null;
|
|
@@ -24164,15 +25346,23 @@ function buildToolUseEvent(update) {
|
|
|
24164
25346
|
})();
|
|
24165
25347
|
const title = (update.title ?? "").trim();
|
|
24166
25348
|
const toolName = title || "Tool";
|
|
24167
|
-
const summaryRaw = summarizeToolInput(update.rawInput, title);
|
|
25349
|
+
const summaryRaw = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
|
|
24168
25350
|
const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
|
|
24169
25351
|
const statusRaw = readString(update, "status");
|
|
24170
25352
|
const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
|
|
25353
|
+
const rawInput = update.rawInput;
|
|
25354
|
+
const content = update.content;
|
|
25355
|
+
const rawOutput = update.rawOutput;
|
|
25356
|
+
const locations = update.locations;
|
|
24171
25357
|
return {
|
|
24172
25358
|
toolCallId,
|
|
24173
25359
|
toolName,
|
|
24174
25360
|
kind,
|
|
24175
25361
|
...summary ? { summary } : {},
|
|
25362
|
+
...rawInput !== undefined ? { rawInput } : {},
|
|
25363
|
+
...content !== undefined ? { content } : {},
|
|
25364
|
+
...rawOutput !== undefined ? { rawOutput } : {},
|
|
25365
|
+
...locations !== undefined ? { locations } : {},
|
|
24176
25366
|
status
|
|
24177
25367
|
};
|
|
24178
25368
|
}
|
|
@@ -24333,6 +25523,8 @@ function buildQueueOwnerPayload(input) {
|
|
|
24333
25523
|
nonInteractivePermissions: input.nonInteractivePermissions,
|
|
24334
25524
|
ttlMs: input.ttlMs ?? 300000,
|
|
24335
25525
|
maxQueueDepth: input.maxQueueDepth ?? 16,
|
|
25526
|
+
...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
|
|
25527
|
+
...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
|
|
24336
25528
|
mcpServers: input.mcpServers
|
|
24337
25529
|
};
|
|
24338
25530
|
}
|
|
@@ -24585,6 +25777,7 @@ class AcpxCliTransport {
|
|
|
24585
25777
|
sessionInitTimeoutMs;
|
|
24586
25778
|
permissionMode;
|
|
24587
25779
|
nonInteractivePermissions;
|
|
25780
|
+
permissionPolicy;
|
|
24588
25781
|
runCommand;
|
|
24589
25782
|
runPtyCommand;
|
|
24590
25783
|
queueOwnerLauncher;
|
|
@@ -24594,6 +25787,7 @@ class AcpxCliTransport {
|
|
|
24594
25787
|
this.sessionInitTimeoutMs = options.sessionInitTimeoutMs ?? 120000;
|
|
24595
25788
|
this.permissionMode = options.permissionMode ?? "approve-all";
|
|
24596
25789
|
this.nonInteractivePermissions = options.nonInteractivePermissions ?? "deny";
|
|
25790
|
+
this.permissionPolicy = options.permissionPolicy;
|
|
24597
25791
|
this.runCommand = runCommand;
|
|
24598
25792
|
this.runPtyCommand = runPtyCommand;
|
|
24599
25793
|
this.queueOwnerLauncher = queueOwnerLauncher ?? new AcpxQueueOwnerLauncher({
|
|
@@ -24613,18 +25807,38 @@ class AcpxCliTransport {
|
|
|
24613
25807
|
timeoutMs: this.sessionInitTimeoutMs
|
|
24614
25808
|
});
|
|
24615
25809
|
}
|
|
25810
|
+
async tailSessionHistory(session, lines) {
|
|
25811
|
+
const candidates = [
|
|
25812
|
+
["sessions", "history", "quiet", "-s", session.transportSession, String(lines)],
|
|
25813
|
+
["sessions", "history", "quiet", session.transportSession, String(lines)],
|
|
25814
|
+
["sessions", "history", "-s", session.transportSession, "--tail", String(lines)],
|
|
25815
|
+
["sessions", "history", session.transportSession, "--tail", String(lines)],
|
|
25816
|
+
["sessions", "history", "--name", session.transportSession, "--tail", String(lines)]
|
|
25817
|
+
];
|
|
25818
|
+
let lastResult;
|
|
25819
|
+
for (const tail2 of candidates) {
|
|
25820
|
+
const args = this.buildArgs(session, tail2);
|
|
25821
|
+
const result = await this.runCommandWithTimeout(this.runCommand, args);
|
|
25822
|
+
if (result.code === 0) {
|
|
25823
|
+
return { text: result.stdout.trimEnd() };
|
|
25824
|
+
}
|
|
25825
|
+
lastResult = result;
|
|
25826
|
+
}
|
|
25827
|
+
const detail = lastResult ? normalizeCommandError(lastResult) ?? `command failed with exit code ${lastResult.code}` : "command failed";
|
|
25828
|
+
throw new Error(detail);
|
|
25829
|
+
}
|
|
24616
25830
|
async prompt(session, text, reply, replyContext, options) {
|
|
24617
25831
|
await this.launchMcpQueueOwnerIfNeeded(session);
|
|
24618
25832
|
const structuredPrompt = await createStructuredPromptFile(text, options?.media);
|
|
24619
25833
|
const args = this.buildPromptArgs(session, text, structuredPrompt?.filePath);
|
|
24620
25834
|
try {
|
|
24621
|
-
if (reply || options?.onSegment || options?.onToolEvent) {
|
|
25835
|
+
if (reply || options?.onSegment || options?.onToolEvent || options?.onThought) {
|
|
24622
25836
|
const formatToolCalls = (session.replyMode ?? "verbose") === "verbose";
|
|
24623
25837
|
let toolEventMode = resolveToolEventMode(options);
|
|
24624
25838
|
if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
|
|
24625
25839
|
toolEventMode = "text";
|
|
24626
25840
|
}
|
|
24627
|
-
const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent);
|
|
25841
|
+
const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent, options?.onThought);
|
|
24628
25842
|
const baseText = getPromptText(result2);
|
|
24629
25843
|
if (!reply) {
|
|
24630
25844
|
return { text: baseText };
|
|
@@ -24664,6 +25878,7 @@ ${baseText}` : "" };
|
|
|
24664
25878
|
async updatePermissionPolicy(policy) {
|
|
24665
25879
|
this.permissionMode = policy.permissionMode;
|
|
24666
25880
|
this.nonInteractivePermissions = policy.nonInteractivePermissions;
|
|
25881
|
+
this.permissionPolicy = policy.permissionPolicy;
|
|
24667
25882
|
}
|
|
24668
25883
|
async removeSession(session) {
|
|
24669
25884
|
const result = await this.runCommand(this.command, this.buildArgs(session, [
|
|
@@ -24761,7 +25976,7 @@ ${baseText}` : "" };
|
|
|
24761
25976
|
})
|
|
24762
25977
|
]);
|
|
24763
25978
|
}
|
|
24764
|
-
async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent) {
|
|
25979
|
+
async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent, onThought) {
|
|
24765
25980
|
const hooks = this.streamingHooks;
|
|
24766
25981
|
const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn9(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
|
|
24767
25982
|
const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
@@ -24779,7 +25994,10 @@ ${baseText}` : "" };
|
|
|
24779
25994
|
let segmentError;
|
|
24780
25995
|
let toolEventChain = Promise.resolve();
|
|
24781
25996
|
let toolEventError;
|
|
25997
|
+
let thoughtChain = Promise.resolve();
|
|
25998
|
+
let thoughtError;
|
|
24782
25999
|
const userOnToolEvent = onToolEvent;
|
|
26000
|
+
const userOnThought = onThought;
|
|
24783
26001
|
const state = createStreamingPromptState(formatToolCalls, {
|
|
24784
26002
|
mode: toolEventMode,
|
|
24785
26003
|
...userOnToolEvent ? {
|
|
@@ -24788,6 +26006,13 @@ ${baseText}` : "" };
|
|
|
24788
26006
|
toolEventError ??= error2;
|
|
24789
26007
|
});
|
|
24790
26008
|
}
|
|
26009
|
+
} : {},
|
|
26010
|
+
...userOnThought ? {
|
|
26011
|
+
onThought: (chunk) => {
|
|
26012
|
+
thoughtChain = thoughtChain.then(() => userOnThought(chunk)).catch((error2) => {
|
|
26013
|
+
thoughtError ??= error2;
|
|
26014
|
+
});
|
|
26015
|
+
}
|
|
24791
26016
|
} : {}
|
|
24792
26017
|
});
|
|
24793
26018
|
const sink = reply ? createQuotaGatedReplySink({
|
|
@@ -24840,7 +26065,8 @@ ${baseText}` : "" };
|
|
|
24840
26065
|
Promise.all([
|
|
24841
26066
|
sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
|
|
24842
26067
|
segmentChain,
|
|
24843
|
-
toolEventChain
|
|
26068
|
+
toolEventChain,
|
|
26069
|
+
thoughtChain
|
|
24844
26070
|
]).then(() => {
|
|
24845
26071
|
const deferred = sink?.getPendingError();
|
|
24846
26072
|
if (deferred) {
|
|
@@ -24855,6 +26081,10 @@ ${baseText}` : "" };
|
|
|
24855
26081
|
reject(toolEventError);
|
|
24856
26082
|
return;
|
|
24857
26083
|
}
|
|
26084
|
+
if (thoughtError) {
|
|
26085
|
+
reject(thoughtError);
|
|
26086
|
+
return;
|
|
26087
|
+
}
|
|
24858
26088
|
resolve3({
|
|
24859
26089
|
result: { code: code ?? 1, stdout: stdout2, stderr },
|
|
24860
26090
|
overflowCount
|
|
@@ -24895,7 +26125,11 @@ ${baseText}` : "" };
|
|
|
24895
26125
|
}
|
|
24896
26126
|
buildPermissionArgs() {
|
|
24897
26127
|
const modeFlag = permissionModeToFlag(this.permissionMode);
|
|
24898
|
-
|
|
26128
|
+
const args = [modeFlag, "--non-interactive-permissions", this.nonInteractivePermissions];
|
|
26129
|
+
if (typeof this.permissionPolicy === "string" && this.permissionPolicy.trim().length > 0) {
|
|
26130
|
+
args.push("--permission-policy", this.permissionPolicy);
|
|
26131
|
+
}
|
|
26132
|
+
return args;
|
|
24899
26133
|
}
|
|
24900
26134
|
}
|
|
24901
26135
|
function isMissingAcpxSessionError(stderr, stdout2) {
|
|
@@ -25418,6 +26652,14 @@ async function buildApp(paths, deps = {}) {
|
|
|
25418
26652
|
resultText: ""
|
|
25419
26653
|
});
|
|
25420
26654
|
}
|
|
26655
|
+
try {
|
|
26656
|
+
await orchestration.reconcileParallelSlots();
|
|
26657
|
+
} catch (reconcileError) {
|
|
26658
|
+
await logger2.error("orchestration.parallel.reconcile_failed", "failed to reconcile parallel slots after worker turn", {
|
|
26659
|
+
taskId: input.taskId,
|
|
26660
|
+
message: reconcileError instanceof Error ? reconcileError.message : String(reconcileError)
|
|
26661
|
+
});
|
|
26662
|
+
}
|
|
25421
26663
|
if (taskRecord && shouldNotifyTaskCompletion(taskRecord)) {
|
|
25422
26664
|
try {
|
|
25423
26665
|
await sendCompletionNotice(taskRecord);
|
|
@@ -25485,6 +26727,13 @@ async function buildApp(paths, deps = {}) {
|
|
|
25485
26727
|
throw new Error(result.message || "worker task cancel was not acknowledged");
|
|
25486
26728
|
}
|
|
25487
26729
|
},
|
|
26730
|
+
closeWorkerSession: async ({ workerSession, targetAgent, workspace, cwd }) => {
|
|
26731
|
+
if (!transport.removeSession) {
|
|
26732
|
+
return;
|
|
26733
|
+
}
|
|
26734
|
+
const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
|
|
26735
|
+
await transport.removeSession(session);
|
|
26736
|
+
},
|
|
25488
26737
|
resumeWorkerTask: async ({ taskId, workerSession, coordinatorSession, targetAgent, workspace, cwd, answer }) => {
|
|
25489
26738
|
launchWorkerTurn({
|
|
25490
26739
|
taskId,
|
|
@@ -25510,7 +26759,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
25510
26759
|
}
|
|
25511
26760
|
},
|
|
25512
26761
|
findReusableWorkerSession: async ({ coordinatorSession, workspace, cwd, targetAgent, role }) => {
|
|
25513
|
-
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.coordinatorSession === coordinatorSession && current.workspace === workspace && current.cwd === cwd && current.targetAgent === targetAgent && current.role === role);
|
|
26762
|
+
const binding = Object.entries(state.orchestration.workerBindings).find(([, current]) => current.ephemeral !== true && current.coordinatorSession === coordinatorSession && current.workspace === workspace && current.cwd === cwd && current.targetAgent === targetAgent && current.role === role);
|
|
25514
26763
|
return binding?.[0] ?? null;
|
|
25515
26764
|
},
|
|
25516
26765
|
logger: logger2
|
|
@@ -25876,7 +27125,11 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
|
25876
27125
|
import { homedir as homedir10 } from "node:os";
|
|
25877
27126
|
async function checkDaemon(options = {}) {
|
|
25878
27127
|
const home = options.home ?? process.env.HOME ?? homedir10();
|
|
25879
|
-
const
|
|
27128
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
27129
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
27130
|
+
home,
|
|
27131
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
27132
|
+
});
|
|
25880
27133
|
const controller = createDaemonController(paths, {
|
|
25881
27134
|
processExecPath: options.processExecPath ?? process.execPath,
|
|
25882
27135
|
cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
|
|
@@ -26028,7 +27281,11 @@ import { dirname as dirname14 } from "node:path";
|
|
|
26028
27281
|
import { homedir as homedir11 } from "node:os";
|
|
26029
27282
|
async function checkRuntime(options = {}) {
|
|
26030
27283
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
26031
|
-
const
|
|
27284
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
27285
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
27286
|
+
home,
|
|
27287
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
27288
|
+
});
|
|
26032
27289
|
const probe = options.probe ?? createRuntimeFsProbe();
|
|
26033
27290
|
const platform = options.platform ?? process.platform;
|
|
26034
27291
|
const checks3 = [
|
|
@@ -26062,107 +27319,107 @@ async function checkRuntime(options = {}) {
|
|
|
26062
27319
|
}
|
|
26063
27320
|
function createRuntimeFsProbe() {
|
|
26064
27321
|
return {
|
|
26065
|
-
stat: async (
|
|
26066
|
-
access: async (
|
|
27322
|
+
stat: async (path15) => await stat3(path15),
|
|
27323
|
+
access: async (path15, mode) => await access4(path15, mode)
|
|
26067
27324
|
};
|
|
26068
27325
|
}
|
|
26069
|
-
async function checkDirectoryCreatable(label,
|
|
27326
|
+
async function checkDirectoryCreatable(label, path15, probe, platform) {
|
|
26070
27327
|
try {
|
|
26071
|
-
const stats = await probe.stat(
|
|
27328
|
+
const stats = await probe.stat(path15);
|
|
26072
27329
|
if (!stats.isDirectory()) {
|
|
26073
27330
|
return {
|
|
26074
27331
|
ok: false,
|
|
26075
|
-
detail: `${label}: ${
|
|
27332
|
+
detail: `${label}: ${path15} (exists but is not a directory)`
|
|
26076
27333
|
};
|
|
26077
27334
|
}
|
|
26078
|
-
await probe.access(
|
|
27335
|
+
await probe.access(path15, directoryAccessMode(platform));
|
|
26079
27336
|
return {
|
|
26080
27337
|
ok: true,
|
|
26081
|
-
detail: `${label}: ${
|
|
27338
|
+
detail: `${label}: ${path15} (writable)`
|
|
26082
27339
|
};
|
|
26083
27340
|
} catch (error2) {
|
|
26084
27341
|
if (!isMissingPathError(error2)) {
|
|
26085
27342
|
return {
|
|
26086
27343
|
ok: false,
|
|
26087
|
-
detail: `${label}: ${
|
|
27344
|
+
detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
|
|
26088
27345
|
};
|
|
26089
27346
|
}
|
|
26090
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
27347
|
+
const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
|
|
26091
27348
|
if (!parentCheck.ok) {
|
|
26092
27349
|
return {
|
|
26093
27350
|
ok: false,
|
|
26094
|
-
detail: `${label}: ${
|
|
27351
|
+
detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
|
|
26095
27352
|
};
|
|
26096
27353
|
}
|
|
26097
27354
|
return {
|
|
26098
27355
|
ok: true,
|
|
26099
|
-
detail: `${label}: ${
|
|
27356
|
+
detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
|
|
26100
27357
|
};
|
|
26101
27358
|
}
|
|
26102
27359
|
}
|
|
26103
|
-
async function checkFileCreatable(label,
|
|
27360
|
+
async function checkFileCreatable(label, path15, probe, platform) {
|
|
26104
27361
|
try {
|
|
26105
|
-
const stats = await probe.stat(
|
|
27362
|
+
const stats = await probe.stat(path15);
|
|
26106
27363
|
if (stats.isDirectory()) {
|
|
26107
27364
|
return {
|
|
26108
27365
|
ok: false,
|
|
26109
|
-
detail: `${label}: ${
|
|
27366
|
+
detail: `${label}: ${path15} (exists but is a directory)`
|
|
26110
27367
|
};
|
|
26111
27368
|
}
|
|
26112
|
-
await probe.access(
|
|
27369
|
+
await probe.access(path15, constants.W_OK);
|
|
26113
27370
|
return {
|
|
26114
27371
|
ok: true,
|
|
26115
|
-
detail: `${label}: ${
|
|
27372
|
+
detail: `${label}: ${path15} (writable)`
|
|
26116
27373
|
};
|
|
26117
27374
|
} catch (error2) {
|
|
26118
27375
|
if (!isMissingPathError(error2)) {
|
|
26119
27376
|
return {
|
|
26120
27377
|
ok: false,
|
|
26121
|
-
detail: `${label}: ${
|
|
27378
|
+
detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
|
|
26122
27379
|
};
|
|
26123
27380
|
}
|
|
26124
|
-
const parentCheck = await checkCreatableAncestorDirectory(dirname14(
|
|
27381
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname14(path15), probe, platform);
|
|
26125
27382
|
if (!parentCheck.ok) {
|
|
26126
27383
|
return {
|
|
26127
27384
|
ok: false,
|
|
26128
|
-
detail: `${label}: ${
|
|
27385
|
+
detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
|
|
26129
27386
|
};
|
|
26130
27387
|
}
|
|
26131
27388
|
return {
|
|
26132
27389
|
ok: true,
|
|
26133
|
-
detail: `${label}: ${
|
|
27390
|
+
detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
|
|
26134
27391
|
};
|
|
26135
27392
|
}
|
|
26136
27393
|
}
|
|
26137
|
-
async function checkCreatableAncestorDirectory(
|
|
27394
|
+
async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
26138
27395
|
try {
|
|
26139
|
-
const stats = await probe.stat(
|
|
27396
|
+
const stats = await probe.stat(path15);
|
|
26140
27397
|
if (!stats.isDirectory()) {
|
|
26141
27398
|
return {
|
|
26142
27399
|
ok: false,
|
|
26143
|
-
creatableFrom:
|
|
26144
|
-
blockingPath:
|
|
27400
|
+
creatableFrom: path15,
|
|
27401
|
+
blockingPath: path15
|
|
26145
27402
|
};
|
|
26146
27403
|
}
|
|
26147
|
-
await probe.access(
|
|
27404
|
+
await probe.access(path15, directoryAccessMode(platform));
|
|
26148
27405
|
return {
|
|
26149
27406
|
ok: true,
|
|
26150
|
-
creatableFrom:
|
|
27407
|
+
creatableFrom: path15
|
|
26151
27408
|
};
|
|
26152
27409
|
} catch (error2) {
|
|
26153
27410
|
if (!isMissingPathError(error2)) {
|
|
26154
27411
|
return {
|
|
26155
27412
|
ok: false,
|
|
26156
|
-
creatableFrom:
|
|
26157
|
-
blockingPath:
|
|
27413
|
+
creatableFrom: path15,
|
|
27414
|
+
blockingPath: path15
|
|
26158
27415
|
};
|
|
26159
27416
|
}
|
|
26160
|
-
const parent = dirname14(
|
|
26161
|
-
if (parent ===
|
|
27417
|
+
const parent = dirname14(path15);
|
|
27418
|
+
if (parent === path15) {
|
|
26162
27419
|
return {
|
|
26163
27420
|
ok: false,
|
|
26164
|
-
creatableFrom:
|
|
26165
|
-
blockingPath:
|
|
27421
|
+
creatableFrom: path15,
|
|
27422
|
+
blockingPath: path15
|
|
26166
27423
|
};
|
|
26167
27424
|
}
|
|
26168
27425
|
const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
|
|
@@ -26591,10 +27848,12 @@ async function runDoctor(options = {}, deps = {}) {
|
|
|
26591
27848
|
resolveRuntimePaths: () => runtimePaths
|
|
26592
27849
|
}));
|
|
26593
27850
|
checks3.push(await (deps.checkRuntime ?? checkRuntime)({
|
|
26594
|
-
home
|
|
27851
|
+
home,
|
|
27852
|
+
configPath: runtimePaths.configPath
|
|
26595
27853
|
}));
|
|
26596
27854
|
checks3.push(await (deps.checkDaemon ?? checkDaemon)({
|
|
26597
|
-
home
|
|
27855
|
+
home,
|
|
27856
|
+
configPath: runtimePaths.configPath
|
|
26598
27857
|
}));
|
|
26599
27858
|
checks3.push(await (deps.checkWechat ?? checkWechat)({
|
|
26600
27859
|
verbose: options.verbose
|
|
@@ -39206,7 +40465,8 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
39206
40465
|
task: exports_external.string().min(1),
|
|
39207
40466
|
workingDirectory: exports_external.string().min(1).optional(),
|
|
39208
40467
|
role: exports_external.string().min(1).optional(),
|
|
39209
|
-
groupId: exports_external.string().min(1).optional()
|
|
40468
|
+
groupId: exports_external.string().min(1).optional(),
|
|
40469
|
+
parallel: exports_external.boolean().describe("Set to true to run this task in its own ephemeral session, concurrently with other in-flight tasks for the same agent.").optional()
|
|
39210
40470
|
}).strict(),
|
|
39211
40471
|
handler: async (args) => await asToolResult(async () => {
|
|
39212
40472
|
const input2 = args;
|
|
@@ -39227,7 +40487,8 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
39227
40487
|
targetAgent: exports_external.string().min(1),
|
|
39228
40488
|
task: exports_external.string().min(1),
|
|
39229
40489
|
workingDirectory: exports_external.string().min(1).optional(),
|
|
39230
|
-
role: exports_external.string().min(1).optional()
|
|
40490
|
+
role: exports_external.string().min(1).optional(),
|
|
40491
|
+
parallel: exports_external.boolean().describe("Set to true to run this task in its own ephemeral session, concurrently with other in-flight tasks for the same agent.").optional()
|
|
39231
40492
|
}).strict()).min(1)
|
|
39232
40493
|
}).strict(),
|
|
39233
40494
|
handler: async (args) => await asToolResult(async () => {
|
|
@@ -39246,7 +40507,8 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
39246
40507
|
task: entry.task,
|
|
39247
40508
|
...entry.workingDirectory ? { workingDirectory: entry.workingDirectory } : {},
|
|
39248
40509
|
...entry.role ? { role: entry.role } : {},
|
|
39249
|
-
...groupId ? { groupId } : {}
|
|
40510
|
+
...groupId ? { groupId } : {},
|
|
40511
|
+
...entry.parallel !== undefined ? { parallel: entry.parallel } : {}
|
|
39250
40512
|
});
|
|
39251
40513
|
results.push({ index, taskId: result.taskId, status: result.status });
|
|
39252
40514
|
} catch (error2) {
|
|
@@ -39470,7 +40732,7 @@ function createErrorResult(message) {
|
|
|
39470
40732
|
};
|
|
39471
40733
|
}
|
|
39472
40734
|
function renderDelegateSuccess(result) {
|
|
39473
|
-
const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, call task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll for the next event or terminal state.`;
|
|
40735
|
+
const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : result.status === "queued" ? `Next: task "${result.taskId}" is queued (agent at parallel capacity). It will start automatically when a slot frees. Call task_watch to long-poll for the transition to running, or task_get/task_list for non-blocking snapshots.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, call task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll for the next event or terminal state.`;
|
|
39474
40736
|
return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
|
|
39475
40737
|
`);
|
|
39476
40738
|
}
|
|
@@ -39741,7 +41003,8 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
39741
41003
|
task: input.task,
|
|
39742
41004
|
...input.workingDirectory !== undefined ? { cwd: input.workingDirectory } : {},
|
|
39743
41005
|
...input.role !== undefined ? { role: input.role } : {},
|
|
39744
|
-
...input.groupId !== undefined ? { groupId: input.groupId } : {}
|
|
41006
|
+
...input.groupId !== undefined ? { groupId: input.groupId } : {},
|
|
41007
|
+
...input.parallel !== undefined ? { parallel: input.parallel } : {}
|
|
39745
41008
|
}),
|
|
39746
41009
|
createGroup: async (input) => await client.createGroup(input),
|
|
39747
41010
|
getTask: async (input) => await client.getTaskForCoordinator(input),
|
|
@@ -40454,10 +41717,12 @@ function parseSourceHandle(args, env = process.env) {
|
|
|
40454
41717
|
|
|
40455
41718
|
// src/cli.ts
|
|
40456
41719
|
init_workspace_path();
|
|
41720
|
+
init_workspace_name();
|
|
40457
41721
|
init_state_store();
|
|
40458
41722
|
|
|
40459
41723
|
// src/onboarding.ts
|
|
40460
41724
|
init_workspace_path();
|
|
41725
|
+
init_workspace_name();
|
|
40461
41726
|
init_agent_templates();
|
|
40462
41727
|
function isFirstUse(config2, state) {
|
|
40463
41728
|
return Object.keys(state.sessions ?? {}).length === 0 && Object.keys(config2.workspaces ?? {}).length === 0 && (config2.plugins ?? []).length === 0;
|
|
@@ -40468,7 +41733,7 @@ async function maybeRunFirstUseOnboarding(input) {
|
|
|
40468
41733
|
if (!input.deps.isInteractive())
|
|
40469
41734
|
return { created: false };
|
|
40470
41735
|
const cwd = normalizeWorkspacePath(input.deps.cwd());
|
|
40471
|
-
const workspaceName =
|
|
41736
|
+
const workspaceName = allocateWorkspaceName(sanitizeWorkspaceName(basenameForWorkspacePath(cwd)), input.config.workspaces);
|
|
40472
41737
|
const yes = (await input.deps.promptText(`检测到首次使用 weacpx。是否将当前目录创建为工作区「${workspaceName}」?[Y/n] `)).trim().toLowerCase();
|
|
40473
41738
|
if (yes === "n" || yes === "no")
|
|
40474
41739
|
return { created: false };
|
|
@@ -40509,18 +41774,6 @@ function resolveTemplateChoice(answer, names) {
|
|
|
40509
41774
|
return names[index - 1];
|
|
40510
41775
|
return names.includes(answer) ? answer : null;
|
|
40511
41776
|
}
|
|
40512
|
-
function allocateName(base, existing) {
|
|
40513
|
-
if (!existing[base])
|
|
40514
|
-
return base;
|
|
40515
|
-
let suffix = 2;
|
|
40516
|
-
while (existing[`${base}-${suffix}`])
|
|
40517
|
-
suffix += 1;
|
|
40518
|
-
return `${base}-${suffix}`;
|
|
40519
|
-
}
|
|
40520
|
-
function sanitizeName(input, fallback) {
|
|
40521
|
-
const sanitized = input.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
40522
|
-
return sanitized || fallback;
|
|
40523
|
-
}
|
|
40524
41777
|
|
|
40525
41778
|
// src/cli-update.ts
|
|
40526
41779
|
init_plugin_home();
|
|
@@ -42184,7 +43437,7 @@ var HELP_LINES = [
|
|
|
42184
43437
|
"weacpx doctor - 运行诊断",
|
|
42185
43438
|
"weacpx version - 查看版本",
|
|
42186
43439
|
"weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
|
|
42187
|
-
"weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
|
|
43440
|
+
"weacpx workspace list|add [name] [--raw]|rm <name> - 管理本机工作区(别名:ws)",
|
|
42188
43441
|
"weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
|
|
42189
43442
|
];
|
|
42190
43443
|
function getUsageText() {
|
|
@@ -42311,7 +43564,7 @@ async function runCli(args, deps = {}) {
|
|
|
42311
43564
|
case "mcp-stdio":
|
|
42312
43565
|
return await (deps.mcpStdio ?? ((subArgs) => defaultMcpStdio(subArgs, { stderr: deps.stderr })))(args.slice(1));
|
|
42313
43566
|
case "start": {
|
|
42314
|
-
const controller = deps.controller ?? createDefaultController();
|
|
43567
|
+
const controller = deps.controller ?? createDefaultController(deps);
|
|
42315
43568
|
try {
|
|
42316
43569
|
const isInteractive = deps.isInteractive ?? defaultIsInteractive;
|
|
42317
43570
|
const status = await controller.getStatus();
|
|
@@ -42354,7 +43607,7 @@ async function runCli(args, deps = {}) {
|
|
|
42354
43607
|
}
|
|
42355
43608
|
}
|
|
42356
43609
|
case "status": {
|
|
42357
|
-
const controller = deps.controller ?? createDefaultController();
|
|
43610
|
+
const controller = deps.controller ?? createDefaultController(deps);
|
|
42358
43611
|
const status = await controller.getStatus();
|
|
42359
43612
|
if (status.state === "indeterminate") {
|
|
42360
43613
|
print("weacpx 进程仍在运行,但状态元数据缺失");
|
|
@@ -42377,7 +43630,7 @@ async function runCli(args, deps = {}) {
|
|
|
42377
43630
|
return 0;
|
|
42378
43631
|
}
|
|
42379
43632
|
case "stop": {
|
|
42380
|
-
const controller = deps.controller ?? createDefaultController();
|
|
43633
|
+
const controller = deps.controller ?? createDefaultController(deps);
|
|
42381
43634
|
const result = await controller.stop();
|
|
42382
43635
|
if (result.detail === "not-running") {
|
|
42383
43636
|
print("weacpx 未运行");
|
|
@@ -42387,7 +43640,7 @@ async function runCli(args, deps = {}) {
|
|
|
42387
43640
|
return 0;
|
|
42388
43641
|
}
|
|
42389
43642
|
case "restart": {
|
|
42390
|
-
const controller = deps.controller ?? createDefaultController();
|
|
43643
|
+
const controller = deps.controller ?? createDefaultController(deps);
|
|
42391
43644
|
try {
|
|
42392
43645
|
return await restartDaemonCli(controller, print);
|
|
42393
43646
|
} catch (error2) {
|
|
@@ -42448,10 +43701,23 @@ async function handleWorkspaceCli(args, deps) {
|
|
|
42448
43701
|
if (args.length !== 1)
|
|
42449
43702
|
return null;
|
|
42450
43703
|
return await workspaceList(deps.print);
|
|
42451
|
-
case "add":
|
|
42452
|
-
|
|
42453
|
-
|
|
42454
|
-
|
|
43704
|
+
case "add": {
|
|
43705
|
+
const rest = args.slice(1);
|
|
43706
|
+
let rawFlag = false;
|
|
43707
|
+
let explicit;
|
|
43708
|
+
for (const token of rest) {
|
|
43709
|
+
if (token === "--raw") {
|
|
43710
|
+
if (rawFlag)
|
|
43711
|
+
return null;
|
|
43712
|
+
rawFlag = true;
|
|
43713
|
+
continue;
|
|
43714
|
+
}
|
|
43715
|
+
if (explicit !== undefined)
|
|
43716
|
+
return null;
|
|
43717
|
+
explicit = token;
|
|
43718
|
+
}
|
|
43719
|
+
return await workspaceAdd(explicit, { ...deps, raw: rawFlag });
|
|
43720
|
+
}
|
|
42455
43721
|
case "rm":
|
|
42456
43722
|
if (args.length !== 2 || !args[1])
|
|
42457
43723
|
return null;
|
|
@@ -42476,13 +43742,20 @@ async function workspaceList(print) {
|
|
|
42476
43742
|
}
|
|
42477
43743
|
async function workspaceAdd(rawName, deps) {
|
|
42478
43744
|
const cwd = normalizeWorkspacePath(deps.cwd());
|
|
42479
|
-
const
|
|
42480
|
-
if (
|
|
43745
|
+
const input = rawName === undefined ? basenameForWorkspacePath(cwd) : rawName.trim();
|
|
43746
|
+
if (input.length === 0) {
|
|
42481
43747
|
deps.print("工作区名称不能为空。");
|
|
42482
43748
|
return 1;
|
|
42483
43749
|
}
|
|
42484
43750
|
const store = await createCliConfigStore();
|
|
42485
43751
|
const config2 = await store.load();
|
|
43752
|
+
let name = input;
|
|
43753
|
+
if (!deps.raw && !isWorkspaceNameValid(input)) {
|
|
43754
|
+
const base = sanitizeWorkspaceName(input);
|
|
43755
|
+
name = allocateWorkspaceName(base, config2.workspaces);
|
|
43756
|
+
const sourceLabel = rawName === undefined ? "目录名" : "名称";
|
|
43757
|
+
deps.print(`${sourceLabel} ${JSON.stringify(input)} 含有特殊字符,已保存为「${name}」。如需保留原名请加 --raw。`);
|
|
43758
|
+
}
|
|
42486
43759
|
const existing = config2.workspaces[name];
|
|
42487
43760
|
if (existing) {
|
|
42488
43761
|
if (sameWorkspacePath(existing.cwd, cwd)) {
|
|
@@ -42490,7 +43763,7 @@ async function workspaceAdd(rawName, deps) {
|
|
|
42490
43763
|
return 0;
|
|
42491
43764
|
}
|
|
42492
43765
|
deps.print(`工作区「${name}」已存在,但路径不同:${existing.cwd}`);
|
|
42493
|
-
deps.print(`请换一个名称,或先执行:weacpx workspace rm ${name}`);
|
|
43766
|
+
deps.print(`请换一个名称,或先执行:weacpx workspace rm ${quoteWorkspaceNameIfNeeded(name)}`);
|
|
42494
43767
|
return 1;
|
|
42495
43768
|
}
|
|
42496
43769
|
await store.upsertWorkspace(name, cwd);
|
|
@@ -42600,8 +43873,18 @@ async function agentRemove(rawName, print) {
|
|
|
42600
43873
|
print(`Agent「${name}」已删除`);
|
|
42601
43874
|
return 0;
|
|
42602
43875
|
}
|
|
43876
|
+
function resolveConfigPathForCurrentEnv() {
|
|
43877
|
+
return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
43878
|
+
}
|
|
43879
|
+
function resolveDaemonPathsForCurrentConfig() {
|
|
43880
|
+
const configPath = resolveConfigPathForCurrentEnv();
|
|
43881
|
+
return resolveDaemonPaths({
|
|
43882
|
+
home: requireHome2(),
|
|
43883
|
+
runtimeDir: resolveRuntimeDirFromConfigPath(configPath)
|
|
43884
|
+
});
|
|
43885
|
+
}
|
|
42603
43886
|
async function createCliConfigStore() {
|
|
42604
|
-
const configPath =
|
|
43887
|
+
const configPath = resolveConfigPathForCurrentEnv();
|
|
42605
43888
|
await ensureConfigExists(configPath);
|
|
42606
43889
|
return new ConfigStore(configPath);
|
|
42607
43890
|
}
|
|
@@ -42636,7 +43919,7 @@ async function defaultRun(options = {}) {
|
|
|
42636
43919
|
await loadConfiguredPlugins2({ plugins: config2.plugins });
|
|
42637
43920
|
const { createMessageChannels: createMessageChannels2 } = await Promise.resolve().then(() => (init_create_channel(), exports_create_channel));
|
|
42638
43921
|
const { MessageChannelRegistry: MessageChannelRegistry2 } = await Promise.resolve().then(() => (init_channel_registry(), exports_channel_registry));
|
|
42639
|
-
const daemonPaths =
|
|
43922
|
+
const daemonPaths = resolveDaemonPathsForCurrentConfig();
|
|
42640
43923
|
const daemonRuntime = new DaemonRuntime(daemonPaths, { pid: process.pid });
|
|
42641
43924
|
const { channelDeps } = await prepareChannelMedia2(runtimePaths.configPath, config2);
|
|
42642
43925
|
const channelRegistry = new MessageChannelRegistry2(createMessageChannels2(config2.channels, channelDeps));
|
|
@@ -42882,13 +44165,14 @@ async function defaultPromptSecret(message) {
|
|
|
42882
44165
|
process.stdin.on("data", onData);
|
|
42883
44166
|
});
|
|
42884
44167
|
}
|
|
42885
|
-
function createDefaultController() {
|
|
42886
|
-
const daemonPaths =
|
|
44168
|
+
function createDefaultController(deps = {}) {
|
|
44169
|
+
const daemonPaths = resolveDaemonPathsForCurrentConfig();
|
|
42887
44170
|
const controller = createDaemonController(daemonPaths, {
|
|
42888
44171
|
processExecPath: process.execPath,
|
|
42889
44172
|
cliEntryPath: resolveCliEntryPath2(),
|
|
42890
44173
|
cwd: process.cwd(),
|
|
42891
|
-
env: process.env
|
|
44174
|
+
env: process.env,
|
|
44175
|
+
...deps.isProcessRunning ? { isProcessRunning: deps.isProcessRunning } : {}
|
|
42892
44176
|
});
|
|
42893
44177
|
return {
|
|
42894
44178
|
getStatus: () => controller.getStatus(),
|
|
@@ -42944,8 +44228,8 @@ function printDaemonLogHints(print) {
|
|
|
42944
44228
|
}
|
|
42945
44229
|
function safeDaemonLogPaths() {
|
|
42946
44230
|
try {
|
|
42947
|
-
const configPath =
|
|
42948
|
-
const paths =
|
|
44231
|
+
const configPath = resolveConfigPathForCurrentEnv();
|
|
44232
|
+
const paths = resolveDaemonPathsForCurrentConfig();
|
|
42949
44233
|
return {
|
|
42950
44234
|
appLog: join16(dirname15(configPath), "runtime", "app.log"),
|
|
42951
44235
|
stderrLog: paths.stderrLog
|