weacpx 0.4.3 → 0.4.4
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/config.example.json +7 -1
- package/dist/channels/types.d.ts +2 -0
- package/dist/cli.js +782 -346
- package/dist/config/types.d.ts +7 -0
- package/dist/logging/rotating-file-writer.d.ts +2 -0
- package/dist/perf/perf-log-writer.d.ts +25 -0
- package/dist/perf/perf-tracer.d.ts +54 -0
- package/dist/weixin/agent/interface.d.ts +7 -0
- package/dist/weixin/bot.d.ts +2 -0
- package/dist/weixin/messaging/handle-weixin-message-turn.d.ts +2 -0
- package/dist/weixin/monitor/monitor.d.ts +2 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2225,6 +2225,28 @@ function parseConfig(raw, options = {}) {
|
|
|
2225
2225
|
throw new Error(`logging.${field} must be a positive number`);
|
|
2226
2226
|
}
|
|
2227
2227
|
}
|
|
2228
|
+
if (isRecord(logging) && "perf" in logging) {
|
|
2229
|
+
if (!isRecord(logging.perf)) {
|
|
2230
|
+
throw new Error("logging.perf must be an object");
|
|
2231
|
+
}
|
|
2232
|
+
if ("enabled" in logging.perf && typeof logging.perf.enabled !== "boolean") {
|
|
2233
|
+
throw new Error("logging.perf.enabled must be boolean");
|
|
2234
|
+
}
|
|
2235
|
+
for (const field of ["maxSizeBytes", "maxFiles", "retentionDays"]) {
|
|
2236
|
+
if (field in logging.perf) {
|
|
2237
|
+
const value = logging.perf[field];
|
|
2238
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2239
|
+
throw new Error(`logging.perf.${field} must be a finite number`);
|
|
2240
|
+
}
|
|
2241
|
+
if (field === "maxFiles" && value < 0) {
|
|
2242
|
+
throw new Error(`logging.perf.${field} must be non-negative`);
|
|
2243
|
+
}
|
|
2244
|
+
if (field !== "maxFiles" && value <= 0) {
|
|
2245
|
+
throw new Error(`logging.perf.${field} must be a positive number`);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2228
2250
|
for (const [name, agent] of Object.entries(raw.agents)) {
|
|
2229
2251
|
if (!isRecord(agent) || typeof agent.driver !== "string" || agent.driver.length === 0) {
|
|
2230
2252
|
throw new Error(`agent "${name}" must define a non-empty driver`);
|
|
@@ -2280,7 +2302,16 @@ function parseConfig(raw, options = {}) {
|
|
|
2280
2302
|
level: resolvedLoggingLevel,
|
|
2281
2303
|
maxSizeBytes: typeof logging?.maxSizeBytes === "number" ? logging.maxSizeBytes : DEFAULT_LOGGING_CONFIG.maxSizeBytes,
|
|
2282
2304
|
maxFiles: typeof logging?.maxFiles === "number" ? logging.maxFiles : DEFAULT_LOGGING_CONFIG.maxFiles,
|
|
2283
|
-
retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays
|
|
2305
|
+
retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays,
|
|
2306
|
+
perf: (() => {
|
|
2307
|
+
const perfRaw = isRecord(logging?.perf) ? logging.perf : undefined;
|
|
2308
|
+
return {
|
|
2309
|
+
enabled: typeof perfRaw?.enabled === "boolean" ? perfRaw.enabled : DEFAULT_PERF_LOG_CONFIG.enabled,
|
|
2310
|
+
maxSizeBytes: typeof perfRaw?.maxSizeBytes === "number" && Number.isFinite(perfRaw.maxSizeBytes) && perfRaw.maxSizeBytes > 0 ? perfRaw.maxSizeBytes : DEFAULT_PERF_LOG_CONFIG.maxSizeBytes,
|
|
2311
|
+
maxFiles: typeof perfRaw?.maxFiles === "number" && Number.isFinite(perfRaw.maxFiles) && perfRaw.maxFiles >= 0 ? perfRaw.maxFiles : DEFAULT_PERF_LOG_CONFIG.maxFiles,
|
|
2312
|
+
retentionDays: typeof perfRaw?.retentionDays === "number" && Number.isFinite(perfRaw.retentionDays) && perfRaw.retentionDays > 0 ? perfRaw.retentionDays : DEFAULT_PERF_LOG_CONFIG.retentionDays
|
|
2313
|
+
};
|
|
2314
|
+
})()
|
|
2284
2315
|
},
|
|
2285
2316
|
channel: channelConfig,
|
|
2286
2317
|
channels: channelsConfig,
|
|
@@ -2390,14 +2421,21 @@ function parseOrchestrationConfig(raw) {
|
|
|
2390
2421
|
progressHeartbeatSeconds: typeof raw.progressHeartbeatSeconds === "number" && Number.isFinite(raw.progressHeartbeatSeconds) ? raw.progressHeartbeatSeconds : DEFAULT_ORCHESTRATION_CONFIG.progressHeartbeatSeconds
|
|
2391
2422
|
};
|
|
2392
2423
|
}
|
|
2393
|
-
var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_CHANNEL_CONFIG, DEFAULT_ORCHESTRATION_CONFIG;
|
|
2424
|
+
var DEFAULT_PERF_LOG_CONFIG, DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_CHANNEL_CONFIG, DEFAULT_ORCHESTRATION_CONFIG;
|
|
2394
2425
|
var init_load_config = __esm(() => {
|
|
2395
2426
|
init_workspace_path();
|
|
2427
|
+
DEFAULT_PERF_LOG_CONFIG = {
|
|
2428
|
+
enabled: false,
|
|
2429
|
+
maxSizeBytes: 5 * 1024 * 1024,
|
|
2430
|
+
maxFiles: 3,
|
|
2431
|
+
retentionDays: 7
|
|
2432
|
+
};
|
|
2396
2433
|
DEFAULT_LOGGING_CONFIG = {
|
|
2397
2434
|
level: "info",
|
|
2398
2435
|
maxSizeBytes: 2 * 1024 * 1024,
|
|
2399
2436
|
maxFiles: 5,
|
|
2400
|
-
retentionDays: 7
|
|
2437
|
+
retentionDays: 7,
|
|
2438
|
+
perf: DEFAULT_PERF_LOG_CONFIG
|
|
2401
2439
|
};
|
|
2402
2440
|
DEFAULT_CHANNEL_CONFIG = {
|
|
2403
2441
|
type: "weixin",
|
|
@@ -2552,7 +2590,13 @@ var init_ensure_config = __esm(() => {
|
|
|
2552
2590
|
level: "info",
|
|
2553
2591
|
maxSizeBytes: 2 * 1024 * 1024,
|
|
2554
2592
|
maxFiles: 5,
|
|
2555
|
-
retentionDays: 7
|
|
2593
|
+
retentionDays: 7,
|
|
2594
|
+
perf: {
|
|
2595
|
+
enabled: false,
|
|
2596
|
+
maxSizeBytes: 5242880,
|
|
2597
|
+
maxFiles: 3,
|
|
2598
|
+
retentionDays: 7
|
|
2599
|
+
}
|
|
2556
2600
|
},
|
|
2557
2601
|
channel: {
|
|
2558
2602
|
type: "weixin",
|
|
@@ -2837,6 +2881,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2837
2881
|
WEACPX_DAEMON_ARG0: options.cliEntryPath,
|
|
2838
2882
|
WEACPX_DAEMON_ARG1: "run",
|
|
2839
2883
|
WEACPX_DAEMON_CWD: options.cwd,
|
|
2884
|
+
WEACPX_DAEMON_RUN: "1",
|
|
2840
2885
|
WEACPX_DAEMON_STDOUT: paths.stdoutLog,
|
|
2841
2886
|
WEACPX_DAEMON_STDERR: paths.stderrLog,
|
|
2842
2887
|
...spawnOptions.firstRunOnboarding ? { WEACPX_FIRST_RUN_ONBOARDING: spawnOptions.firstRunOnboarding } : {}
|
|
@@ -2855,6 +2900,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2855
2900
|
detached: true,
|
|
2856
2901
|
env: {
|
|
2857
2902
|
...options.env,
|
|
2903
|
+
WEACPX_DAEMON_RUN: "1",
|
|
2858
2904
|
...spawnOptions.firstRunOnboarding ? { WEACPX_FIRST_RUN_ONBOARDING: spawnOptions.firstRunOnboarding } : {}
|
|
2859
2905
|
},
|
|
2860
2906
|
stdio: ["ignore", stdoutFd, stderrFd]
|
|
@@ -2863,6 +2909,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2863
2909
|
}
|
|
2864
2910
|
function buildWindowsLauncherScript() {
|
|
2865
2911
|
const script = [
|
|
2912
|
+
"$env:WEACPX_DAEMON_RUN = '1'",
|
|
2866
2913
|
"$process = Start-Process -FilePath $env:WEACPX_DAEMON_COMMAND `",
|
|
2867
2914
|
" -ArgumentList @($env:WEACPX_DAEMON_ARG0, $env:WEACPX_DAEMON_ARG1) `",
|
|
2868
2915
|
" -WorkingDirectory $env:WEACPX_DAEMON_CWD `",
|
|
@@ -13147,6 +13194,296 @@ function normalizeMediaArray(media) {
|
|
|
13147
13194
|
return Array.isArray(media) ? media : [media];
|
|
13148
13195
|
}
|
|
13149
13196
|
|
|
13197
|
+
// src/logging/rotating-file-writer.ts
|
|
13198
|
+
import { readdir as readdir2, rename, rm as rm5, stat as stat2 } from "node:fs/promises";
|
|
13199
|
+
import { basename, dirname as dirname6, join as join4 } from "node:path";
|
|
13200
|
+
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
13201
|
+
let currentSize = 0;
|
|
13202
|
+
try {
|
|
13203
|
+
currentSize = (await stat2(filePath)).size;
|
|
13204
|
+
} catch (error2) {
|
|
13205
|
+
if (!isMissingFileError2(error2)) {
|
|
13206
|
+
throw error2;
|
|
13207
|
+
}
|
|
13208
|
+
}
|
|
13209
|
+
if (currentSize + incomingSize <= maxSizeBytes) {
|
|
13210
|
+
return;
|
|
13211
|
+
}
|
|
13212
|
+
if (currentSize === 0) {
|
|
13213
|
+
return;
|
|
13214
|
+
}
|
|
13215
|
+
if (maxFiles <= 0) {
|
|
13216
|
+
await rm5(filePath, { force: true });
|
|
13217
|
+
return;
|
|
13218
|
+
}
|
|
13219
|
+
await rm5(`${filePath}.${maxFiles}`, { force: true });
|
|
13220
|
+
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
13221
|
+
const source = `${filePath}.${index}`;
|
|
13222
|
+
try {
|
|
13223
|
+
await rename(source, `${filePath}.${index + 1}`);
|
|
13224
|
+
} catch (error2) {
|
|
13225
|
+
if (!isMissingFileError2(error2)) {
|
|
13226
|
+
throw error2;
|
|
13227
|
+
}
|
|
13228
|
+
}
|
|
13229
|
+
}
|
|
13230
|
+
await rename(filePath, `${filePath}.1`);
|
|
13231
|
+
}
|
|
13232
|
+
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
13233
|
+
const parentDir = dirname6(filePath);
|
|
13234
|
+
const prefix = `${basename(filePath)}.`;
|
|
13235
|
+
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
13236
|
+
let files = [];
|
|
13237
|
+
try {
|
|
13238
|
+
files = await readdir2(parentDir);
|
|
13239
|
+
} catch (error2) {
|
|
13240
|
+
if (isMissingFileError2(error2)) {
|
|
13241
|
+
return;
|
|
13242
|
+
}
|
|
13243
|
+
throw error2;
|
|
13244
|
+
}
|
|
13245
|
+
for (const file of files) {
|
|
13246
|
+
if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
|
|
13247
|
+
continue;
|
|
13248
|
+
}
|
|
13249
|
+
const candidate = join4(parentDir, file);
|
|
13250
|
+
const details = await stat2(candidate);
|
|
13251
|
+
if (details.mtime.getTime() < cutoff) {
|
|
13252
|
+
await rm5(candidate, { force: true });
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
13255
|
+
}
|
|
13256
|
+
function isMissingFileError2(error2) {
|
|
13257
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
13258
|
+
}
|
|
13259
|
+
var init_rotating_file_writer = () => {};
|
|
13260
|
+
|
|
13261
|
+
// src/perf/perf-log-writer.ts
|
|
13262
|
+
import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
13263
|
+
import { dirname as dirname7 } from "node:path";
|
|
13264
|
+
function createPerfLogWriter(options) {
|
|
13265
|
+
const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
|
|
13266
|
+
const mkdir8 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
|
|
13267
|
+
return;
|
|
13268
|
+
}));
|
|
13269
|
+
const now = options.now ?? (() => new Date);
|
|
13270
|
+
const threshold = options.failureThreshold ?? 5;
|
|
13271
|
+
let pending = [];
|
|
13272
|
+
let writeChain = Promise.resolve();
|
|
13273
|
+
let consecutiveFailures = 0;
|
|
13274
|
+
let disabled = false;
|
|
13275
|
+
let notified = false;
|
|
13276
|
+
const writer = {
|
|
13277
|
+
enqueue(line) {
|
|
13278
|
+
if (disabled)
|
|
13279
|
+
return;
|
|
13280
|
+
pending.push(line);
|
|
13281
|
+
scheduleDrain();
|
|
13282
|
+
},
|
|
13283
|
+
async flush() {
|
|
13284
|
+
await scheduleDrain();
|
|
13285
|
+
await writeChain;
|
|
13286
|
+
},
|
|
13287
|
+
async cleanup() {
|
|
13288
|
+
if (disabled)
|
|
13289
|
+
return;
|
|
13290
|
+
try {
|
|
13291
|
+
await cleanupExpiredRotatedLogs(options.filePath, options.retentionDays ?? 7, now);
|
|
13292
|
+
} catch {}
|
|
13293
|
+
},
|
|
13294
|
+
isDisabled() {
|
|
13295
|
+
return disabled;
|
|
13296
|
+
}
|
|
13297
|
+
};
|
|
13298
|
+
return writer;
|
|
13299
|
+
function scheduleDrain() {
|
|
13300
|
+
if (disabled || pending.length === 0) {
|
|
13301
|
+
return writeChain;
|
|
13302
|
+
}
|
|
13303
|
+
const batch = pending;
|
|
13304
|
+
pending = [];
|
|
13305
|
+
writeChain = writeChain.catch(() => {}).then(() => drainBatch(batch));
|
|
13306
|
+
return writeChain;
|
|
13307
|
+
}
|
|
13308
|
+
async function drainBatch(batch) {
|
|
13309
|
+
if (disabled)
|
|
13310
|
+
return;
|
|
13311
|
+
const data = batch.join("");
|
|
13312
|
+
try {
|
|
13313
|
+
await mkdir8(dirname7(options.filePath), { recursive: true });
|
|
13314
|
+
await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
|
|
13315
|
+
await append(options.filePath, data);
|
|
13316
|
+
consecutiveFailures = 0;
|
|
13317
|
+
} catch (err) {
|
|
13318
|
+
consecutiveFailures += 1;
|
|
13319
|
+
if (consecutiveFailures >= threshold) {
|
|
13320
|
+
disabled = true;
|
|
13321
|
+
pending = [];
|
|
13322
|
+
if (!notified) {
|
|
13323
|
+
notified = true;
|
|
13324
|
+
options.onPermanentFailure({
|
|
13325
|
+
perfLogPath: options.filePath,
|
|
13326
|
+
failureCount: consecutiveFailures,
|
|
13327
|
+
lastError: err instanceof Error ? err.message : String(err)
|
|
13328
|
+
});
|
|
13329
|
+
}
|
|
13330
|
+
}
|
|
13331
|
+
}
|
|
13332
|
+
}
|
|
13333
|
+
}
|
|
13334
|
+
var init_perf_log_writer = __esm(() => {
|
|
13335
|
+
init_rotating_file_writer();
|
|
13336
|
+
});
|
|
13337
|
+
|
|
13338
|
+
// src/perf/perf-tracer.ts
|
|
13339
|
+
import { randomBytes } from "node:crypto";
|
|
13340
|
+
function createNoopPerfTracer() {
|
|
13341
|
+
return {
|
|
13342
|
+
async wrapTurn(_seed, run) {
|
|
13343
|
+
return run(NOOP_SPAN);
|
|
13344
|
+
},
|
|
13345
|
+
async flush() {},
|
|
13346
|
+
async cleanup() {}
|
|
13347
|
+
};
|
|
13348
|
+
}
|
|
13349
|
+
function createPerfTracer(options) {
|
|
13350
|
+
const now = options.now ?? (() => performance.now());
|
|
13351
|
+
const isoNow = options.isoNow ?? (() => new Date);
|
|
13352
|
+
const randomId = options.randomId ?? defaultRandomId;
|
|
13353
|
+
const formatLine = options.formatLine ?? defaultFormatLine;
|
|
13354
|
+
const formatSummary = options.formatSummaryLine ?? defaultFormatSummaryLine;
|
|
13355
|
+
let disabled = false;
|
|
13356
|
+
const writer = createPerfLogWriter({
|
|
13357
|
+
filePath: options.filePath,
|
|
13358
|
+
maxSizeBytes: options.maxSizeBytes,
|
|
13359
|
+
maxFiles: options.maxFiles,
|
|
13360
|
+
retentionDays: options.retentionDays,
|
|
13361
|
+
onPermanentFailure: (info) => {
|
|
13362
|
+
disabled = true;
|
|
13363
|
+
options.appLogger.error("perf.disabled_due_to_io_error", "perf logging disabled after repeated IO failures", {
|
|
13364
|
+
perfLogPath: info.perfLogPath,
|
|
13365
|
+
failureCount: info.failureCount,
|
|
13366
|
+
lastError: info.lastError
|
|
13367
|
+
}).catch(() => {});
|
|
13368
|
+
}
|
|
13369
|
+
});
|
|
13370
|
+
return {
|
|
13371
|
+
async wrapTurn(seed, run) {
|
|
13372
|
+
if (disabled) {
|
|
13373
|
+
return run(NOOP_SPAN);
|
|
13374
|
+
}
|
|
13375
|
+
const traceId = randomId();
|
|
13376
|
+
let startTime;
|
|
13377
|
+
const marks = [];
|
|
13378
|
+
let lastMarkTime;
|
|
13379
|
+
let explicitOutcome;
|
|
13380
|
+
let outcomeContext;
|
|
13381
|
+
const span = {
|
|
13382
|
+
traceId,
|
|
13383
|
+
mark(event, context) {
|
|
13384
|
+
if (disabled)
|
|
13385
|
+
return;
|
|
13386
|
+
try {
|
|
13387
|
+
const t = now();
|
|
13388
|
+
if (startTime === undefined) {
|
|
13389
|
+
startTime = t;
|
|
13390
|
+
lastMarkTime = t;
|
|
13391
|
+
}
|
|
13392
|
+
const since = t - startTime;
|
|
13393
|
+
const sinceLast = t - lastMarkTime;
|
|
13394
|
+
lastMarkTime = t;
|
|
13395
|
+
marks.push({ e: event, t: Math.round(since) });
|
|
13396
|
+
const line = formatLine({
|
|
13397
|
+
isoNow: isoNow(),
|
|
13398
|
+
event,
|
|
13399
|
+
traceId,
|
|
13400
|
+
chatKey: seed.chatKey,
|
|
13401
|
+
sinceStartMs: Math.round(since),
|
|
13402
|
+
sinceLastMs: Math.round(sinceLast),
|
|
13403
|
+
context
|
|
13404
|
+
});
|
|
13405
|
+
writer.enqueue(line);
|
|
13406
|
+
} catch {}
|
|
13407
|
+
},
|
|
13408
|
+
setOutcome(outcome, context) {
|
|
13409
|
+
explicitOutcome = outcome;
|
|
13410
|
+
outcomeContext = context;
|
|
13411
|
+
}
|
|
13412
|
+
};
|
|
13413
|
+
let thrown;
|
|
13414
|
+
try {
|
|
13415
|
+
return await run(span);
|
|
13416
|
+
} catch (err) {
|
|
13417
|
+
thrown = err;
|
|
13418
|
+
throw err;
|
|
13419
|
+
} finally {
|
|
13420
|
+
try {
|
|
13421
|
+
if (!disabled) {
|
|
13422
|
+
let outcome;
|
|
13423
|
+
if (explicitOutcome !== undefined) {
|
|
13424
|
+
outcome = explicitOutcome;
|
|
13425
|
+
} else if (thrown !== undefined) {
|
|
13426
|
+
outcome = "error";
|
|
13427
|
+
} else {
|
|
13428
|
+
outcome = "ok";
|
|
13429
|
+
}
|
|
13430
|
+
const t = now();
|
|
13431
|
+
const effectiveStart = startTime ?? t;
|
|
13432
|
+
const summary = formatSummary({
|
|
13433
|
+
isoNow: isoNow(),
|
|
13434
|
+
traceId,
|
|
13435
|
+
chatKey: seed.chatKey,
|
|
13436
|
+
kind: seed.kind,
|
|
13437
|
+
outcome,
|
|
13438
|
+
totalMs: Math.round(t - effectiveStart),
|
|
13439
|
+
marks,
|
|
13440
|
+
outcomeContext
|
|
13441
|
+
});
|
|
13442
|
+
writer.enqueue(summary);
|
|
13443
|
+
}
|
|
13444
|
+
} catch {}
|
|
13445
|
+
}
|
|
13446
|
+
},
|
|
13447
|
+
async flush() {
|
|
13448
|
+
await writer.flush();
|
|
13449
|
+
},
|
|
13450
|
+
async cleanup() {
|
|
13451
|
+
await writer.cleanup();
|
|
13452
|
+
}
|
|
13453
|
+
};
|
|
13454
|
+
}
|
|
13455
|
+
function defaultRandomId() {
|
|
13456
|
+
return randomBytes(6).toString("hex");
|
|
13457
|
+
}
|
|
13458
|
+
function defaultFormatLine(args) {
|
|
13459
|
+
const ctxFields = args.context ? Object.entries(args.context).filter(([, v]) => v !== undefined).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ") : "";
|
|
13460
|
+
const ctxPrefix = ctxFields ? ` ${ctxFields}` : "";
|
|
13461
|
+
return `${args.isoNow.toISOString()} PERF ${args.event} trace=${args.traceId} chatKey=${formatValue(args.chatKey)}${ctxPrefix} sinceStartMs=${args.sinceStartMs} sinceLastMs=${args.sinceLastMs}
|
|
13462
|
+
`;
|
|
13463
|
+
}
|
|
13464
|
+
function defaultFormatSummaryLine(args) {
|
|
13465
|
+
const extra = args.outcomeContext ? " " + Object.entries(args.outcomeContext).filter(([, v]) => v !== undefined).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ") : "";
|
|
13466
|
+
const marksJson = JSON.stringify(args.marks);
|
|
13467
|
+
return `${args.isoNow.toISOString()} PERF turn.done trace=${args.traceId} chatKey=${formatValue(args.chatKey)} kind=${formatValue(args.kind)} outcome=${formatValue(args.outcome)} totalMs=${args.totalMs}${extra} marks=${JSON.stringify(marksJson)}
|
|
13468
|
+
`;
|
|
13469
|
+
}
|
|
13470
|
+
function formatValue(value) {
|
|
13471
|
+
if (value === null)
|
|
13472
|
+
return "null";
|
|
13473
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
13474
|
+
return String(value);
|
|
13475
|
+
return JSON.stringify(value);
|
|
13476
|
+
}
|
|
13477
|
+
var NOOP_SPAN;
|
|
13478
|
+
var init_perf_tracer = __esm(() => {
|
|
13479
|
+
init_perf_log_writer();
|
|
13480
|
+
NOOP_SPAN = {
|
|
13481
|
+
traceId: "-",
|
|
13482
|
+
mark: () => {},
|
|
13483
|
+
setOutcome: () => {}
|
|
13484
|
+
};
|
|
13485
|
+
});
|
|
13486
|
+
|
|
13150
13487
|
// src/weixin/messaging/handle-weixin-message-turn.ts
|
|
13151
13488
|
import crypto4 from "node:crypto";
|
|
13152
13489
|
import fs8 from "node:fs/promises";
|
|
@@ -13278,6 +13615,9 @@ function isClearSlashCommand(textBody) {
|
|
|
13278
13615
|
const command = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
13279
13616
|
return command === "/clear";
|
|
13280
13617
|
}
|
|
13618
|
+
function isSlashCommandText(textBody) {
|
|
13619
|
+
return textBody.startsWith("/");
|
|
13620
|
+
}
|
|
13281
13621
|
function getWeixinMessageTurnLane(full) {
|
|
13282
13622
|
const textBody = extractTextBody(full.item_list).trim().toLowerCase();
|
|
13283
13623
|
return textBody === "/cancel" || textBody === "/stop" || textBody === "/jx" ? "control" : "normal";
|
|
@@ -13343,230 +13683,300 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
13343
13683
|
}
|
|
13344
13684
|
}).catch(() => {});
|
|
13345
13685
|
};
|
|
13346
|
-
const
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
accountId: deps.accountId,
|
|
13363
|
-
log: deps.log,
|
|
13364
|
-
errLog: deps.errLog,
|
|
13365
|
-
onClear: () => deps.agent.clearSession?.(chatKey),
|
|
13366
|
-
...deps.hasPendingFinal ? { hasPendingFinal: deps.hasPendingFinal } : {},
|
|
13367
|
-
...deps.drainPendingFinal ? { drainPendingFinal: deps.drainPendingFinal } : {},
|
|
13368
|
-
...deps.prependPendingFinal ? { prependPendingFinal: deps.prependPendingFinal } : {},
|
|
13369
|
-
...deps.reserveFinal ? { reserveFinal: deps.reserveFinal } : {},
|
|
13370
|
-
...deps.finalRemaining ? { finalRemaining: deps.finalRemaining } : {}
|
|
13371
|
-
}, receivedAt, full.create_time_ms);
|
|
13372
|
-
if (slashResult.handled)
|
|
13373
|
-
return;
|
|
13374
|
-
} finally {
|
|
13686
|
+
const chatKey = buildWeixinChatKey(deps.accountId, fromUserId);
|
|
13687
|
+
const initialMediaCount = extractWeixinMediaDescriptors(full.item_list).length;
|
|
13688
|
+
const isSlashCommand = isSlashCommandText(textBody);
|
|
13689
|
+
const tracer = deps.perfTracer ?? createNoopPerfTracer();
|
|
13690
|
+
return await tracer.wrapTurn({ chatKey, kind: isSlashCommand ? "command" : "prompt" }, async (perfSpan) => {
|
|
13691
|
+
perfSpan.mark("turn.received", {
|
|
13692
|
+
textLen: textBody.length,
|
|
13693
|
+
hasMedia: initialMediaCount > 0,
|
|
13694
|
+
mediaCount: initialMediaCount
|
|
13695
|
+
});
|
|
13696
|
+
const contextToken = full.context_token;
|
|
13697
|
+
if (contextToken) {
|
|
13698
|
+
setContextToken(deps.accountId, full.from_user_id ?? "", contextToken);
|
|
13699
|
+
}
|
|
13700
|
+
if (isSlashCommand) {
|
|
13701
|
+
const shouldTypeForSlash = isClearSlashCommand(textBody);
|
|
13375
13702
|
if (shouldTypeForSlash) {
|
|
13376
|
-
|
|
13377
|
-
}
|
|
13378
|
-
}
|
|
13379
|
-
}
|
|
13380
|
-
startTypingIndicator();
|
|
13381
|
-
const mediaStore = deps.mediaStore ?? new RuntimeMediaStore({ rootDir: resolveMediaTempDir(deps.mediaTempDir) });
|
|
13382
|
-
const media = [];
|
|
13383
|
-
const attachmentNotes = [];
|
|
13384
|
-
const descriptors = extractWeixinMediaDescriptors(full.item_list).slice(0, DEFAULT_MAX_ATTACHMENTS_PER_MESSAGE);
|
|
13385
|
-
const download = deps.downloadMediaFromItemFn ?? downloadMediaFromItem;
|
|
13386
|
-
for (const descriptor of descriptors) {
|
|
13387
|
-
try {
|
|
13388
|
-
const downloaded = await download(descriptor.item, {
|
|
13389
|
-
cdnBaseUrl: deps.cdnBaseUrl,
|
|
13390
|
-
saveMedia: createSaveMediaBuffer(deps.mediaTempDir),
|
|
13391
|
-
log: deps.log,
|
|
13392
|
-
errLog: deps.errLog,
|
|
13393
|
-
label: "inbound"
|
|
13394
|
-
});
|
|
13395
|
-
const filePath = downloaded.decryptedPicPath ?? downloaded.decryptedVideoPath ?? downloaded.decryptedFilePath ?? downloaded.decryptedVoicePath;
|
|
13396
|
-
if (!filePath) {
|
|
13397
|
-
attachmentNotes.push(`Skipped ${descriptor.kind}: media was unavailable.`);
|
|
13398
|
-
continue;
|
|
13703
|
+
startTypingIndicator();
|
|
13399
13704
|
}
|
|
13400
13705
|
try {
|
|
13401
|
-
const
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13706
|
+
const slashResult = await handleSlashCommand(textBody, {
|
|
13707
|
+
to,
|
|
13708
|
+
contextToken: full.context_token,
|
|
13709
|
+
baseUrl: deps.baseUrl,
|
|
13710
|
+
token: deps.token,
|
|
13405
13711
|
accountId: deps.accountId,
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13712
|
+
log: deps.log,
|
|
13713
|
+
errLog: deps.errLog,
|
|
13714
|
+
onClear: () => deps.agent.clearSession?.(chatKey),
|
|
13715
|
+
...deps.hasPendingFinal ? { hasPendingFinal: deps.hasPendingFinal } : {},
|
|
13716
|
+
...deps.drainPendingFinal ? { drainPendingFinal: deps.drainPendingFinal } : {},
|
|
13717
|
+
...deps.prependPendingFinal ? { prependPendingFinal: deps.prependPendingFinal } : {},
|
|
13718
|
+
...deps.reserveFinal ? { reserveFinal: deps.reserveFinal } : {},
|
|
13719
|
+
...deps.finalRemaining ? { finalRemaining: deps.finalRemaining } : {}
|
|
13720
|
+
}, receivedAt, full.create_time_ms);
|
|
13721
|
+
if (slashResult.handled)
|
|
13722
|
+
return;
|
|
13414
13723
|
} finally {
|
|
13415
|
-
|
|
13724
|
+
if (shouldTypeForSlash) {
|
|
13725
|
+
stopTypingIndicator();
|
|
13726
|
+
}
|
|
13416
13727
|
}
|
|
13417
|
-
} catch (err) {
|
|
13418
|
-
deps.errLog(`media download failed: ${String(err)}`);
|
|
13419
|
-
attachmentNotes.push(`Skipped ${descriptor.kind}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13420
13728
|
}
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
const
|
|
13424
|
-
|
|
13425
|
-
|
|
13729
|
+
startTypingIndicator();
|
|
13730
|
+
const mediaStore = deps.mediaStore ?? new RuntimeMediaStore({ rootDir: resolveMediaTempDir(deps.mediaTempDir) });
|
|
13731
|
+
const media = [];
|
|
13732
|
+
const attachmentNotes = [];
|
|
13733
|
+
const descriptors = extractWeixinMediaDescriptors(full.item_list).slice(0, DEFAULT_MAX_ATTACHMENTS_PER_MESSAGE);
|
|
13734
|
+
const download = deps.downloadMediaFromItemFn ?? downloadMediaFromItem;
|
|
13735
|
+
for (const descriptor of descriptors) {
|
|
13736
|
+
try {
|
|
13737
|
+
const downloaded = await download(descriptor.item, {
|
|
13738
|
+
cdnBaseUrl: deps.cdnBaseUrl,
|
|
13739
|
+
saveMedia: createSaveMediaBuffer(deps.mediaTempDir),
|
|
13740
|
+
log: deps.log,
|
|
13741
|
+
errLog: deps.errLog,
|
|
13742
|
+
label: "inbound"
|
|
13743
|
+
});
|
|
13744
|
+
const filePath = downloaded.decryptedPicPath ?? downloaded.decryptedVideoPath ?? downloaded.decryptedFilePath ?? downloaded.decryptedVoicePath;
|
|
13745
|
+
if (!filePath) {
|
|
13746
|
+
attachmentNotes.push(`Skipped ${descriptor.kind}: media was unavailable.`);
|
|
13747
|
+
continue;
|
|
13748
|
+
}
|
|
13749
|
+
try {
|
|
13750
|
+
const buffer = await fs8.readFile(filePath);
|
|
13751
|
+
const mimeType = downloaded.fileMediaType ?? downloaded.voiceMediaType ?? defaultWeixinMime(descriptor.kind);
|
|
13752
|
+
media.push(await mediaStore.saveMediaBuffer({
|
|
13753
|
+
channelId: "weixin",
|
|
13754
|
+
accountId: deps.accountId,
|
|
13755
|
+
chatKey: buildWeixinChatKey(deps.accountId, full.from_user_id ?? ""),
|
|
13756
|
+
messageId: full.message_id ? String(full.message_id) : full.context_token ?? String(full.create_time_ms ?? Date.now()),
|
|
13757
|
+
fileName: descriptor.fileName,
|
|
13758
|
+
mimeType,
|
|
13759
|
+
kind: descriptor.kind,
|
|
13760
|
+
buffer,
|
|
13761
|
+
maxBytes: descriptor.kind === "image" ? DEFAULT_IMAGE_MAX_BYTES : DEFAULT_ATTACHMENT_MAX_BYTES
|
|
13762
|
+
}));
|
|
13763
|
+
} finally {
|
|
13764
|
+
await fs8.rm(filePath, { force: true }).catch(() => {});
|
|
13765
|
+
}
|
|
13766
|
+
} catch (err) {
|
|
13767
|
+
deps.errLog(`media download failed: ${String(err)}`);
|
|
13768
|
+
attachmentNotes.push(`Skipped ${descriptor.kind}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13769
|
+
}
|
|
13426
13770
|
}
|
|
13771
|
+
let midFirstSent = false;
|
|
13772
|
+
const sendReplySegment = async (text) => {
|
|
13773
|
+
const plainText = markdownToPlainText(text).trim();
|
|
13774
|
+
if (plainText.length === 0) {
|
|
13775
|
+
return false;
|
|
13776
|
+
}
|
|
13777
|
+
try {
|
|
13778
|
+
await sendMessageWeixin({
|
|
13779
|
+
to,
|
|
13780
|
+
text: plainText,
|
|
13781
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13782
|
+
});
|
|
13783
|
+
if (!midFirstSent) {
|
|
13784
|
+
midFirstSent = true;
|
|
13785
|
+
perfSpan.mark("reply.mid_first_sent", { bytes: utf8ByteLength(plainText) });
|
|
13786
|
+
}
|
|
13787
|
+
return true;
|
|
13788
|
+
} catch (err) {
|
|
13789
|
+
deps.errLog(`intermediate reply failed: ${String(err)}`);
|
|
13790
|
+
return false;
|
|
13791
|
+
}
|
|
13792
|
+
};
|
|
13793
|
+
const requestText = appendAttachmentNotes(bodyFromItemList(full.item_list), attachmentNotes);
|
|
13794
|
+
const request = {
|
|
13795
|
+
accountId: deps.accountId,
|
|
13796
|
+
conversationId: buildWeixinChatKey(deps.accountId, full.from_user_id ?? ""),
|
|
13797
|
+
text: requestText,
|
|
13798
|
+
...media.length > 0 ? { media } : {},
|
|
13799
|
+
replyContextToken: contextToken,
|
|
13800
|
+
perfSpan
|
|
13801
|
+
};
|
|
13427
13802
|
try {
|
|
13428
|
-
await
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13803
|
+
const turn = await executeChatTurn({
|
|
13804
|
+
agent: deps.agent,
|
|
13805
|
+
request,
|
|
13806
|
+
onReplySegment: sendReplySegment
|
|
13432
13807
|
});
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
replyContextToken: contextToken
|
|
13446
|
-
};
|
|
13447
|
-
try {
|
|
13448
|
-
const turn = await executeChatTurn({
|
|
13449
|
-
agent: deps.agent,
|
|
13450
|
-
request,
|
|
13451
|
-
onReplySegment: sendReplySegment
|
|
13452
|
-
});
|
|
13453
|
-
const outboundMedia = normalizeMediaArray(turn.media);
|
|
13454
|
-
if (turn.text) {
|
|
13455
|
-
const finalText = markdownToPlainText(turn.text).trim();
|
|
13456
|
-
if (finalText.length > 0) {
|
|
13457
|
-
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
13458
|
-
if (rawChunks.length > 0) {
|
|
13459
|
-
const total = rawChunks.length;
|
|
13460
|
-
if (total === 1) {
|
|
13461
|
-
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13462
|
-
if (!reserved) {
|
|
13463
|
-
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
13464
|
-
} else {
|
|
13465
|
-
await sendMessageWeixin({
|
|
13466
|
-
to,
|
|
13467
|
-
text: rawChunks[0],
|
|
13468
|
-
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13469
|
-
});
|
|
13470
|
-
}
|
|
13471
|
-
} else {
|
|
13472
|
-
const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
|
|
13473
|
-
const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
|
|
13474
|
-
const waveSize = Math.max(Math.min(available, total), 0);
|
|
13475
|
-
const wave = prefixed.slice(0, waveSize);
|
|
13476
|
-
const rest = prefixed.slice(waveSize);
|
|
13477
|
-
if (wave.length > 0 && rest.length > 0) {
|
|
13478
|
-
const sentSoFar = wave.length;
|
|
13479
|
-
wave[wave.length - 1] = `${wave[wave.length - 1]}
|
|
13480
|
-
|
|
13481
|
-
${buildFinalHeadsUp({
|
|
13482
|
-
total,
|
|
13483
|
-
sentSoFar
|
|
13484
|
-
})}`;
|
|
13485
|
-
}
|
|
13486
|
-
let sent = 0;
|
|
13487
|
-
for (let i = 0;i < wave.length; i += 1) {
|
|
13808
|
+
const outboundMedia = normalizeMediaArray(turn.media);
|
|
13809
|
+
let finalFirstSent = false;
|
|
13810
|
+
let finalChunksSent = 0;
|
|
13811
|
+
let finalChunksPending = 0;
|
|
13812
|
+
let finalDropped = false;
|
|
13813
|
+
if (turn.text) {
|
|
13814
|
+
const finalText = markdownToPlainText(turn.text).trim();
|
|
13815
|
+
if (finalText.length > 0) {
|
|
13816
|
+
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
13817
|
+
if (rawChunks.length > 0) {
|
|
13818
|
+
const total = rawChunks.length;
|
|
13819
|
+
if (total === 1) {
|
|
13488
13820
|
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13489
13821
|
if (!reserved) {
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
}
|
|
13493
|
-
try {
|
|
13822
|
+
finalDropped = true;
|
|
13823
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
13824
|
+
} else {
|
|
13494
13825
|
await sendMessageWeixin({
|
|
13495
13826
|
to,
|
|
13496
|
-
text:
|
|
13827
|
+
text: rawChunks[0],
|
|
13497
13828
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13498
13829
|
});
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13830
|
+
finalChunksSent += 1;
|
|
13831
|
+
if (!finalFirstSent) {
|
|
13832
|
+
finalFirstSent = true;
|
|
13833
|
+
perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(rawChunks[0]), chunkIndex: 1 });
|
|
13834
|
+
}
|
|
13835
|
+
}
|
|
13836
|
+
} else {
|
|
13837
|
+
const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
|
|
13838
|
+
const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
|
|
13839
|
+
const waveSize = Math.max(Math.min(available, total), 0);
|
|
13840
|
+
const wave = prefixed.slice(0, waveSize);
|
|
13841
|
+
const rest = prefixed.slice(waveSize);
|
|
13842
|
+
if (wave.length > 0 && rest.length > 0) {
|
|
13843
|
+
const sentSoFar = wave.length;
|
|
13844
|
+
wave[wave.length - 1] = `${wave[wave.length - 1]}
|
|
13845
|
+
|
|
13846
|
+
${buildFinalHeadsUp({
|
|
13847
|
+
total,
|
|
13848
|
+
sentSoFar
|
|
13849
|
+
})}`;
|
|
13850
|
+
}
|
|
13851
|
+
let sent = 0;
|
|
13852
|
+
for (let i = 0;i < wave.length; i += 1) {
|
|
13853
|
+
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13854
|
+
if (!reserved) {
|
|
13855
|
+
finalDropped = true;
|
|
13856
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text_paginated chatKey=${to} chunk=${i + 1}/${total}`);
|
|
13857
|
+
break;
|
|
13858
|
+
}
|
|
13859
|
+
try {
|
|
13860
|
+
await sendMessageWeixin({
|
|
13861
|
+
to,
|
|
13862
|
+
text: wave[i],
|
|
13863
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13864
|
+
});
|
|
13865
|
+
sent += 1;
|
|
13866
|
+
finalChunksSent += 1;
|
|
13867
|
+
if (!finalFirstSent) {
|
|
13868
|
+
finalFirstSent = true;
|
|
13869
|
+
perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(wave[i]), chunkIndex: i + 1 });
|
|
13870
|
+
}
|
|
13871
|
+
} catch (sendErr) {
|
|
13872
|
+
finalDropped = true;
|
|
13873
|
+
deps.errLog(`weixin.final.dropped reason=send_failed kind=text_paginated chatKey=${to} chunk=${i + 1}/${total} err=${String(sendErr)}`);
|
|
13874
|
+
break;
|
|
13875
|
+
}
|
|
13876
|
+
}
|
|
13877
|
+
const restToPark = prefixed.slice(sent);
|
|
13878
|
+
finalChunksPending = restToPark.length;
|
|
13879
|
+
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
13880
|
+
const pending = restToPark.map((text, idx) => {
|
|
13881
|
+
const seq = sent + idx + 1;
|
|
13882
|
+
const entry = { text, seq, total };
|
|
13883
|
+
if (contextToken !== undefined)
|
|
13884
|
+
entry.contextToken = contextToken;
|
|
13885
|
+
if (deps.accountId !== undefined)
|
|
13886
|
+
entry.accountId = deps.accountId;
|
|
13887
|
+
return entry;
|
|
13888
|
+
});
|
|
13889
|
+
deps.enqueuePendingFinal(to, pending);
|
|
13503
13890
|
}
|
|
13504
|
-
}
|
|
13505
|
-
const restToPark = prefixed.slice(sent);
|
|
13506
|
-
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
13507
|
-
const pending = restToPark.map((text, idx) => {
|
|
13508
|
-
const seq = sent + idx + 1;
|
|
13509
|
-
const entry = { text, seq, total };
|
|
13510
|
-
if (contextToken !== undefined)
|
|
13511
|
-
entry.contextToken = contextToken;
|
|
13512
|
-
if (deps.accountId !== undefined)
|
|
13513
|
-
entry.accountId = deps.accountId;
|
|
13514
|
-
return entry;
|
|
13515
|
-
});
|
|
13516
|
-
deps.enqueuePendingFinal(to, pending);
|
|
13517
13891
|
}
|
|
13518
13892
|
}
|
|
13519
13893
|
}
|
|
13894
|
+
perfSpan.mark("reply.final_done", {
|
|
13895
|
+
chunksSent: finalChunksSent,
|
|
13896
|
+
chunksPending: finalChunksPending,
|
|
13897
|
+
dropped: finalDropped
|
|
13898
|
+
});
|
|
13520
13899
|
}
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13900
|
+
let mediaSent = 0;
|
|
13901
|
+
let mediaFailed = 0;
|
|
13902
|
+
let mediaRejected = 0;
|
|
13903
|
+
let mediaDropped = 0;
|
|
13904
|
+
for (const mediaItem of outboundMedia) {
|
|
13905
|
+
const filePath = await resolveSafeOutboundMediaPath(mediaItem.filePath, [mediaStore.rootDir, resolveMediaTempDir(deps.mediaTempDir), ...deps.allowedMediaRoots ?? []]);
|
|
13906
|
+
if (!filePath) {
|
|
13907
|
+
mediaRejected += 1;
|
|
13908
|
+
deps.errLog(`outbound media rejected: path=${mediaItem.filePath}`);
|
|
13909
|
+
continue;
|
|
13910
|
+
}
|
|
13911
|
+
const caption = mediaItem.caption ? markdownToPlainText(mediaItem.caption) : "";
|
|
13912
|
+
const captionReserve = caption && deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13913
|
+
if (!captionReserve) {
|
|
13914
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=media_caption chatKey=${to}`);
|
|
13915
|
+
}
|
|
13916
|
+
const reservedMedia = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13917
|
+
if (!reservedMedia) {
|
|
13918
|
+
mediaDropped += 1;
|
|
13919
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=media chatKey=${to}`);
|
|
13920
|
+
continue;
|
|
13921
|
+
}
|
|
13922
|
+
try {
|
|
13923
|
+
const sent = await sendWeixinMediaFile({
|
|
13924
|
+
media: mediaItem,
|
|
13925
|
+
filePath,
|
|
13926
|
+
to,
|
|
13927
|
+
text: captionReserve ? caption : "",
|
|
13928
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken },
|
|
13929
|
+
cdnBaseUrl: deps.cdnBaseUrl
|
|
13930
|
+
});
|
|
13931
|
+
mediaSent += 1;
|
|
13932
|
+
perfSpan.mark("reply.media_sent", {
|
|
13933
|
+
kind: mediaItem.kind,
|
|
13934
|
+
index: mediaSent + mediaFailed + mediaRejected + mediaDropped,
|
|
13935
|
+
messageId: sent.messageId
|
|
13936
|
+
});
|
|
13937
|
+
} catch (err) {
|
|
13938
|
+
mediaFailed += 1;
|
|
13939
|
+
deps.errLog(`outbound media send failed: ${String(err)}`);
|
|
13940
|
+
}
|
|
13527
13941
|
}
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13942
|
+
if (outboundMedia.length > 0) {
|
|
13943
|
+
perfSpan.mark("reply.media_done", {
|
|
13944
|
+
mediaCount: outboundMedia.length,
|
|
13945
|
+
sent: mediaSent,
|
|
13946
|
+
failed: mediaFailed,
|
|
13947
|
+
rejected: mediaRejected,
|
|
13948
|
+
dropped: mediaDropped
|
|
13949
|
+
});
|
|
13532
13950
|
}
|
|
13533
|
-
|
|
13534
|
-
if (
|
|
13535
|
-
|
|
13536
|
-
|
|
13951
|
+
} catch (err) {
|
|
13952
|
+
if (isAbortError(err)) {
|
|
13953
|
+
perfSpan.setOutcome("aborted", { reason: "user_cancel" });
|
|
13954
|
+
deps.log(`handleWeixinMessageTurn: turn aborted: ${err.message}`);
|
|
13955
|
+
return;
|
|
13537
13956
|
}
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13957
|
+
perfSpan.setOutcome("error", { reason: "turn_error" });
|
|
13958
|
+
const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
|
|
13959
|
+
deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
|
|
13960
|
+
const reservedErr = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13961
|
+
if (!reservedErr) {
|
|
13962
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=error_notice chatKey=${to}`);
|
|
13963
|
+
} else {
|
|
13964
|
+
sendWeixinErrorNotice({
|
|
13542
13965
|
to,
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13966
|
+
contextToken,
|
|
13967
|
+
message: `⚠️ 过程失败:${err instanceof Error ? err.message : JSON.stringify(err)}`,
|
|
13968
|
+
baseUrl: deps.baseUrl,
|
|
13969
|
+
token: deps.token,
|
|
13970
|
+
errLog: deps.errLog
|
|
13546
13971
|
});
|
|
13547
|
-
} catch (err) {
|
|
13548
|
-
deps.errLog(`outbound media send failed: ${String(err)}`);
|
|
13549
13972
|
}
|
|
13973
|
+
} finally {
|
|
13974
|
+
stopTypingIndicator();
|
|
13550
13975
|
}
|
|
13551
|
-
}
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
if (!reservedErr) {
|
|
13556
|
-
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=error_notice chatKey=${to}`);
|
|
13557
|
-
} else {
|
|
13558
|
-
sendWeixinErrorNotice({
|
|
13559
|
-
to,
|
|
13560
|
-
contextToken,
|
|
13561
|
-
message: `⚠️ 过程失败:${err instanceof Error ? err.message : JSON.stringify(err)}`,
|
|
13562
|
-
baseUrl: deps.baseUrl,
|
|
13563
|
-
token: deps.token,
|
|
13564
|
-
errLog: deps.errLog
|
|
13565
|
-
});
|
|
13566
|
-
}
|
|
13567
|
-
} finally {
|
|
13568
|
-
stopTypingIndicator();
|
|
13569
|
-
}
|
|
13976
|
+
});
|
|
13977
|
+
}
|
|
13978
|
+
function isAbortError(error2) {
|
|
13979
|
+
return error2 instanceof Error && error2.name === "AbortError";
|
|
13570
13980
|
}
|
|
13571
13981
|
var MAX_FINAL_CHUNK_BYTES = 1800;
|
|
13572
13982
|
var init_handle_weixin_message_turn = __esm(() => {
|
|
@@ -13581,6 +13991,7 @@ var init_handle_weixin_message_turn = __esm(() => {
|
|
|
13581
13991
|
init_send_media();
|
|
13582
13992
|
init_send();
|
|
13583
13993
|
init_slash_commands();
|
|
13994
|
+
init_perf_tracer();
|
|
13584
13995
|
});
|
|
13585
13996
|
|
|
13586
13997
|
// src/weixin/storage/sync-buf.ts
|
|
@@ -13769,7 +14180,8 @@ async function monitorWeixinProvider(opts) {
|
|
|
13769
14180
|
...opts.drainPendingFinal ? { drainPendingFinal: opts.drainPendingFinal } : {},
|
|
13770
14181
|
...opts.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
|
|
13771
14182
|
...opts.mediaStore ? { mediaStore: opts.mediaStore } : {},
|
|
13772
|
-
...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {}
|
|
14183
|
+
...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {},
|
|
14184
|
+
...opts.perfTracer ? { perfTracer: opts.perfTracer } : {}
|
|
13773
14185
|
})).catch((err) => {
|
|
13774
14186
|
errLog(`[weixin] message turn failed: ${String(err)}`);
|
|
13775
14187
|
});
|
|
@@ -13912,7 +14324,8 @@ async function start(agent, opts) {
|
|
|
13912
14324
|
...opts?.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
|
|
13913
14325
|
...opts?.enqueuePendingFinal ? { enqueuePendingFinal: opts.enqueuePendingFinal } : {},
|
|
13914
14326
|
...opts?.dropPendingFinal ? { dropPendingFinal: opts.dropPendingFinal } : {},
|
|
13915
|
-
...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {}
|
|
14327
|
+
...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {},
|
|
14328
|
+
...opts?.perfTracer ? { perfTracer: opts.perfTracer } : {}
|
|
13916
14329
|
});
|
|
13917
14330
|
}
|
|
13918
14331
|
var init_bot = __esm(() => {
|
|
@@ -14168,16 +14581,16 @@ var init_deliver_coordinator_message = __esm(() => {
|
|
|
14168
14581
|
});
|
|
14169
14582
|
|
|
14170
14583
|
// src/weixin/monitor/consumer-lock.ts
|
|
14171
|
-
import { mkdir as mkdir8, open as open2, readFile as readFile6, rm as
|
|
14172
|
-
import { dirname as
|
|
14584
|
+
import { mkdir as mkdir8, open as open2, readFile as readFile6, rm as rm6 } from "node:fs/promises";
|
|
14585
|
+
import { dirname as dirname8, join as join5 } from "node:path";
|
|
14173
14586
|
import { homedir as homedir4 } from "node:os";
|
|
14174
14587
|
function createWeixinConsumerLock(options = {}) {
|
|
14175
|
-
const lockFilePath = options.lockFilePath ??
|
|
14588
|
+
const lockFilePath = options.lockFilePath ?? join5(homedir4(), ".weacpx", "runtime", "weixin-consumer.lock.json");
|
|
14176
14589
|
const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
|
|
14177
14590
|
const onDiagnostic = options.onDiagnostic;
|
|
14178
14591
|
return {
|
|
14179
14592
|
async acquire(meta2) {
|
|
14180
|
-
await mkdir8(
|
|
14593
|
+
await mkdir8(dirname8(lockFilePath), { recursive: true });
|
|
14181
14594
|
while (true) {
|
|
14182
14595
|
try {
|
|
14183
14596
|
const handle = await open2(lockFilePath, "wx");
|
|
@@ -14208,7 +14621,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14208
14621
|
});
|
|
14209
14622
|
const existing = await loadLockMetadata(lockFilePath);
|
|
14210
14623
|
if (!existing) {
|
|
14211
|
-
await
|
|
14624
|
+
await rm6(lockFilePath, { force: true });
|
|
14212
14625
|
await onDiagnostic?.("lock_invalid_removed", {
|
|
14213
14626
|
lockFilePath,
|
|
14214
14627
|
reason: "invalid_or_unreadable_metadata"
|
|
@@ -14216,7 +14629,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14216
14629
|
continue;
|
|
14217
14630
|
}
|
|
14218
14631
|
if (!isProcessRunning(existing.pid)) {
|
|
14219
|
-
await
|
|
14632
|
+
await rm6(lockFilePath, { force: true });
|
|
14220
14633
|
await onDiagnostic?.("lock_stale_removed", {
|
|
14221
14634
|
lockFilePath,
|
|
14222
14635
|
stalePid: existing.pid,
|
|
@@ -14241,7 +14654,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14241
14654
|
}
|
|
14242
14655
|
},
|
|
14243
14656
|
async release() {
|
|
14244
|
-
await
|
|
14657
|
+
await rm6(lockFilePath, { force: true });
|
|
14245
14658
|
await onDiagnostic?.("lock_released", {
|
|
14246
14659
|
lockFilePath
|
|
14247
14660
|
});
|
|
@@ -14340,7 +14753,8 @@ class WeixinChannel {
|
|
|
14340
14753
|
drainPendingFinal: (chatKey, available) => input.quota.drainPendingFinalUpToBudget(chatKey, available),
|
|
14341
14754
|
prependPendingFinal: (chatKey, chunks) => input.quota.prependPendingFinal(chatKey, chunks),
|
|
14342
14755
|
enqueuePendingFinal: (chatKey, chunks) => input.quota.enqueuePendingFinal(chatKey, chunks),
|
|
14343
|
-
dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey)
|
|
14756
|
+
dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey),
|
|
14757
|
+
...input.perfTracer ? { perfTracer: input.perfTracer } : {}
|
|
14344
14758
|
});
|
|
14345
14759
|
}
|
|
14346
14760
|
async notifyTaskCompletion(task) {
|
|
@@ -14849,9 +15263,9 @@ __export(exports_plugin_loader, {
|
|
|
14849
15263
|
});
|
|
14850
15264
|
import { createRequire as createRequire2 } from "node:module";
|
|
14851
15265
|
import { pathToFileURL } from "node:url";
|
|
14852
|
-
import { join as
|
|
15266
|
+
import { join as join6 } from "node:path";
|
|
14853
15267
|
async function importPluginFromHome(packageName, pluginHome) {
|
|
14854
|
-
const requireFromHome = createRequire2(
|
|
15268
|
+
const requireFromHome = createRequire2(join6(pluginHome, "package.json"));
|
|
14855
15269
|
const entry = requireFromHome.resolve(packageName);
|
|
14856
15270
|
return await import(pathToFileURL(entry).href);
|
|
14857
15271
|
}
|
|
@@ -14899,8 +15313,8 @@ var init_bootstrap = __esm(() => {
|
|
|
14899
15313
|
});
|
|
14900
15314
|
|
|
14901
15315
|
// src/logging/app-logger.ts
|
|
14902
|
-
import { appendFile, mkdir as mkdir9
|
|
14903
|
-
import {
|
|
15316
|
+
import { appendFile, mkdir as mkdir9 } from "node:fs/promises";
|
|
15317
|
+
import { dirname as dirname10 } from "node:path";
|
|
14904
15318
|
function createNoopAppLogger() {
|
|
14905
15319
|
return {
|
|
14906
15320
|
debug: async () => {},
|
|
@@ -14940,74 +15354,18 @@ function createAppLogger(options) {
|
|
|
14940
15354
|
return;
|
|
14941
15355
|
}
|
|
14942
15356
|
const line = formatLogLine(now(), level, event, message, context);
|
|
14943
|
-
await mkdir9(
|
|
15357
|
+
await mkdir9(dirname10(options.filePath), { recursive: true });
|
|
14944
15358
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
14945
15359
|
await appendFile(options.filePath, line, "utf8");
|
|
14946
15360
|
}
|
|
14947
15361
|
}
|
|
14948
|
-
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
14949
|
-
let currentSize = 0;
|
|
14950
|
-
try {
|
|
14951
|
-
currentSize = (await stat2(filePath)).size;
|
|
14952
|
-
} catch (error2) {
|
|
14953
|
-
if (!isMissingFileError2(error2)) {
|
|
14954
|
-
throw error2;
|
|
14955
|
-
}
|
|
14956
|
-
}
|
|
14957
|
-
if (currentSize + incomingSize <= maxSizeBytes) {
|
|
14958
|
-
return;
|
|
14959
|
-
}
|
|
14960
|
-
if (currentSize === 0) {
|
|
14961
|
-
return;
|
|
14962
|
-
}
|
|
14963
|
-
if (maxFiles <= 0) {
|
|
14964
|
-
await rm6(filePath, { force: true });
|
|
14965
|
-
return;
|
|
14966
|
-
}
|
|
14967
|
-
await rm6(`${filePath}.${maxFiles}`, { force: true });
|
|
14968
|
-
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
14969
|
-
const source = `${filePath}.${index}`;
|
|
14970
|
-
try {
|
|
14971
|
-
await rename(source, `${filePath}.${index + 1}`);
|
|
14972
|
-
} catch (error2) {
|
|
14973
|
-
if (!isMissingFileError2(error2)) {
|
|
14974
|
-
throw error2;
|
|
14975
|
-
}
|
|
14976
|
-
}
|
|
14977
|
-
}
|
|
14978
|
-
await rename(filePath, `${filePath}.1`);
|
|
14979
|
-
}
|
|
14980
|
-
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
14981
|
-
const parentDir = dirname8(filePath);
|
|
14982
|
-
const prefix = `${basename(filePath)}.`;
|
|
14983
|
-
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
14984
|
-
let files = [];
|
|
14985
|
-
try {
|
|
14986
|
-
files = await readdir2(parentDir);
|
|
14987
|
-
} catch (error2) {
|
|
14988
|
-
if (isMissingFileError2(error2)) {
|
|
14989
|
-
return;
|
|
14990
|
-
}
|
|
14991
|
-
throw error2;
|
|
14992
|
-
}
|
|
14993
|
-
for (const file of files) {
|
|
14994
|
-
if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
|
|
14995
|
-
continue;
|
|
14996
|
-
}
|
|
14997
|
-
const candidate = join9(parentDir, file);
|
|
14998
|
-
const details = await stat2(candidate);
|
|
14999
|
-
if (details.mtime.getTime() < cutoff) {
|
|
15000
|
-
await rm6(candidate, { force: true });
|
|
15001
|
-
}
|
|
15002
|
-
}
|
|
15003
|
-
}
|
|
15004
15362
|
function formatLogLine(time3, level, event, message, context) {
|
|
15005
|
-
const fields = Object.entries(context).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${
|
|
15363
|
+
const fields = Object.entries(context).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${formatValue2(value)}`);
|
|
15006
15364
|
const suffix = fields.length > 0 ? ` ${fields.join(" ")}` : "";
|
|
15007
|
-
return `${time3.toISOString()} ${level.toUpperCase()} ${event} message=${
|
|
15365
|
+
return `${time3.toISOString()} ${level.toUpperCase()} ${event} message=${formatValue2(message)}${suffix}
|
|
15008
15366
|
`;
|
|
15009
15367
|
}
|
|
15010
|
-
function
|
|
15368
|
+
function formatValue2(value) {
|
|
15011
15369
|
if (value === null) {
|
|
15012
15370
|
return "null";
|
|
15013
15371
|
}
|
|
@@ -15016,11 +15374,9 @@ function formatValue(value) {
|
|
|
15016
15374
|
}
|
|
15017
15375
|
return JSON.stringify(value);
|
|
15018
15376
|
}
|
|
15019
|
-
function isMissingFileError2(error2) {
|
|
15020
|
-
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
15021
|
-
}
|
|
15022
15377
|
var LEVEL_ORDER;
|
|
15023
15378
|
var init_app_logger = __esm(() => {
|
|
15379
|
+
init_rotating_file_writer();
|
|
15024
15380
|
LEVEL_ORDER = {
|
|
15025
15381
|
error: 0,
|
|
15026
15382
|
info: 1,
|
|
@@ -16440,6 +16796,7 @@ async function handleSessionAttach(context, chatKey, alias, agent, workspace, tr
|
|
|
16440
16796
|
`)
|
|
16441
16797
|
};
|
|
16442
16798
|
}
|
|
16799
|
+
context.lifecycle.markSessionReady?.(attached);
|
|
16443
16800
|
await context.sessions.attachSession(internalAlias, agent, workspace, transportSession);
|
|
16444
16801
|
await context.sessions.useSession(chatKey, internalAlias);
|
|
16445
16802
|
await refreshSessionTransportAgentCommandBestEffort(context, internalAlias, "session.attach.agent_command_refresh_failed");
|
|
@@ -16636,7 +16993,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
16636
16993
|
return { text: lines.join(`
|
|
16637
16994
|
`) };
|
|
16638
16995
|
}
|
|
16639
|
-
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
|
|
16996
|
+
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
16640
16997
|
const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
|
|
16641
16998
|
if (!session.replyMode)
|
|
16642
16999
|
session.replyMode = effectiveReplyMode;
|
|
@@ -16661,7 +17018,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
16661
17018
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
|
|
16662
17019
|
try {
|
|
16663
17020
|
const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
|
|
16664
|
-
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent);
|
|
17021
|
+
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, perfSpan);
|
|
16665
17022
|
if (claimHumanReply) {
|
|
16666
17023
|
try {
|
|
16667
17024
|
await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
|
|
@@ -16681,17 +17038,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
16681
17038
|
throw error2;
|
|
16682
17039
|
}
|
|
16683
17040
|
}
|
|
16684
|
-
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
|
|
17041
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
16685
17042
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
16686
17043
|
if (!session) {
|
|
16687
17044
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
16688
17045
|
}
|
|
16689
17046
|
try {
|
|
16690
|
-
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
17047
|
+
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
16691
17048
|
} catch (error2) {
|
|
16692
17049
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
|
|
16693
17050
|
if (recovered) {
|
|
16694
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
17051
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
16695
17052
|
}
|
|
16696
17053
|
return context.recovery.renderTransportError(session, error2);
|
|
16697
17054
|
}
|
|
@@ -17955,7 +18312,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
17955
18312
|
import { createRequire as createRequire3 } from "node:module";
|
|
17956
18313
|
import { access as access3 } from "node:fs/promises";
|
|
17957
18314
|
import { homedir as homedir7 } from "node:os";
|
|
17958
|
-
import { dirname as
|
|
18315
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
17959
18316
|
function deriveParentPackageName(platformPackage) {
|
|
17960
18317
|
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
17961
18318
|
}
|
|
@@ -18028,7 +18385,7 @@ function defaultResolveFromCwd(name, cwd) {
|
|
|
18028
18385
|
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
18029
18386
|
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
18030
18387
|
});
|
|
18031
|
-
return
|
|
18388
|
+
return dirname11(pkgJson);
|
|
18032
18389
|
} catch {
|
|
18033
18390
|
return null;
|
|
18034
18391
|
}
|
|
@@ -18186,7 +18543,7 @@ class CommandRouter {
|
|
|
18186
18543
|
this.quota = quota;
|
|
18187
18544
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
18188
18545
|
}
|
|
18189
|
-
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent) {
|
|
18546
|
+
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, perfSpan) {
|
|
18190
18547
|
const startedAt = Date.now();
|
|
18191
18548
|
const command = parseCommand(input);
|
|
18192
18549
|
await this.logger.debug("command.parsed", "parsed inbound command", {
|
|
@@ -18194,6 +18551,7 @@ class CommandRouter {
|
|
|
18194
18551
|
kind: command.kind
|
|
18195
18552
|
});
|
|
18196
18553
|
const access4 = authorizeCommandForChat(command, metadata);
|
|
18554
|
+
perfSpan?.mark("router.authorized", { decision: access4.allowed ? "allow" : "deny" });
|
|
18197
18555
|
if (!access4.allowed) {
|
|
18198
18556
|
await this.logger.info("command.blocked", "blocked command by chat policy", {
|
|
18199
18557
|
chatKey,
|
|
@@ -18205,6 +18563,7 @@ class CommandRouter {
|
|
|
18205
18563
|
return { text: renderCommandAccessDenied(command) };
|
|
18206
18564
|
}
|
|
18207
18565
|
await this.refreshConfigFromStore();
|
|
18566
|
+
perfSpan?.mark("router.config_refreshed");
|
|
18208
18567
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
18209
18568
|
switch (command.kind) {
|
|
18210
18569
|
case "invalid":
|
|
@@ -18247,35 +18606,35 @@ class CommandRouter {
|
|
|
18247
18606
|
case "workspace.rm":
|
|
18248
18607
|
return await handleWorkspaceRemove(this.createHandlerContext(), command.name);
|
|
18249
18608
|
case "sessions":
|
|
18250
|
-
return await handleSessions(this.createSessionHandlerContext(), chatKey);
|
|
18609
|
+
return await handleSessions(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18251
18610
|
case "session.new":
|
|
18252
|
-
return await handleSessionNew(this.createSessionHandlerContext(reply), chatKey, command.alias, command.agent, command.workspace);
|
|
18611
|
+
return await handleSessionNew(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace);
|
|
18253
18612
|
case "session.shortcut":
|
|
18254
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, false);
|
|
18613
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, false);
|
|
18255
18614
|
case "session.shortcut.new":
|
|
18256
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, true);
|
|
18615
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
|
|
18257
18616
|
case "session.attach":
|
|
18258
|
-
return await handleSessionAttach(this.createSessionHandlerContext(reply), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
|
|
18617
|
+
return await handleSessionAttach(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
|
|
18259
18618
|
case "session.use":
|
|
18260
|
-
return await handleSessionUse(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18619
|
+
return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18261
18620
|
case "mode.show":
|
|
18262
|
-
return await handleModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18621
|
+
return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18263
18622
|
case "mode.set":
|
|
18264
|
-
return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
|
|
18623
|
+
return await handleModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.modeId);
|
|
18265
18624
|
case "replymode.show":
|
|
18266
|
-
return await handleReplyModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18625
|
+
return await handleReplyModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18267
18626
|
case "replymode.set":
|
|
18268
|
-
return await handleReplyModeSet(this.createSessionHandlerContext(), chatKey, command.replyMode);
|
|
18627
|
+
return await handleReplyModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.replyMode);
|
|
18269
18628
|
case "replymode.reset":
|
|
18270
|
-
return await handleReplyModeReset(this.createSessionHandlerContext(), chatKey);
|
|
18629
|
+
return await handleReplyModeReset(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18271
18630
|
case "status":
|
|
18272
|
-
return await handleStatus(this.createSessionHandlerContext(), chatKey);
|
|
18631
|
+
return await handleStatus(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18273
18632
|
case "cancel":
|
|
18274
|
-
return await handleCancel(this.createSessionHandlerContext(), chatKey);
|
|
18633
|
+
return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18275
18634
|
case "session.reset":
|
|
18276
|
-
return await handleSessionReset(this.createSessionHandlerContext(reply), chatKey);
|
|
18635
|
+
return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
|
|
18277
18636
|
case "session.rm":
|
|
18278
|
-
return await handleSessionRemove(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18637
|
+
return await handleSessionRemove(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18279
18638
|
case "groups":
|
|
18280
18639
|
return await handleGroupList(this.createHandlerContext(), chatKey, command.filter);
|
|
18281
18640
|
case "group.new":
|
|
@@ -18301,7 +18660,7 @@ class CommandRouter {
|
|
|
18301
18660
|
case "task.cancel":
|
|
18302
18661
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
18303
18662
|
case "prompt":
|
|
18304
|
-
return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
18663
|
+
return await handlePrompt(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
18305
18664
|
}
|
|
18306
18665
|
});
|
|
18307
18666
|
}
|
|
@@ -18320,23 +18679,24 @@ class CommandRouter {
|
|
|
18320
18679
|
...this.quota ? { quota: this.quota } : {}
|
|
18321
18680
|
};
|
|
18322
18681
|
}
|
|
18323
|
-
createSessionHandlerContext(reply) {
|
|
18682
|
+
createSessionHandlerContext(reply, perfSpan) {
|
|
18324
18683
|
return {
|
|
18325
18684
|
...this.createHandlerContext(),
|
|
18326
|
-
lifecycle: this.createSessionLifecycleOps(reply),
|
|
18327
|
-
interaction: this.createSessionInteractionOps(),
|
|
18685
|
+
lifecycle: this.createSessionLifecycleOps(reply, perfSpan),
|
|
18686
|
+
interaction: this.createSessionInteractionOps(perfSpan),
|
|
18328
18687
|
recovery: this.createSessionRenderRecoveryOps()
|
|
18329
18688
|
};
|
|
18330
18689
|
}
|
|
18331
|
-
createSessionLifecycleOps(reply) {
|
|
18690
|
+
createSessionLifecycleOps(reply, perfSpan) {
|
|
18332
18691
|
return {
|
|
18333
18692
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18334
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18693
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18335
18694
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18695
|
+
markSessionReady: () => perfSpan?.mark("session.ready"),
|
|
18336
18696
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18337
18697
|
handleSessionShortcut: async (chatKey, agent, target, createNew, replyOverride) => {
|
|
18338
18698
|
try {
|
|
18339
|
-
return await handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(replyOverride ?? reply), chatKey, agent, target, createNew);
|
|
18699
|
+
return await handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(replyOverride ?? reply, perfSpan), chatKey, agent, target, createNew);
|
|
18340
18700
|
} catch (err) {
|
|
18341
18701
|
if (err instanceof AutoInstallFailedError) {
|
|
18342
18702
|
const session = this.sessions.resolveSession(`${agent}`, agent, target.workspace ?? "", `${agent}`);
|
|
@@ -18345,15 +18705,15 @@ class CommandRouter {
|
|
|
18345
18705
|
throw err;
|
|
18346
18706
|
}
|
|
18347
18707
|
},
|
|
18348
|
-
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply), chatKey),
|
|
18708
|
+
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply, perfSpan), chatKey),
|
|
18349
18709
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
18350
18710
|
};
|
|
18351
18711
|
}
|
|
18352
|
-
createSessionInteractionOps() {
|
|
18712
|
+
createSessionInteractionOps(perfSpan) {
|
|
18353
18713
|
return {
|
|
18354
18714
|
setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
|
|
18355
18715
|
cancelTransportSession: (session) => this.cancelTransportSession(session),
|
|
18356
|
-
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent)
|
|
18716
|
+
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride ?? perfSpan)
|
|
18357
18717
|
};
|
|
18358
18718
|
}
|
|
18359
18719
|
createSessionRenderRecoveryOps() {
|
|
@@ -18364,9 +18724,9 @@ class CommandRouter {
|
|
|
18364
18724
|
renderTransportError: (session, error2) => renderTransportError(session, error2)
|
|
18365
18725
|
};
|
|
18366
18726
|
}
|
|
18367
|
-
createSessionResetOps(reply) {
|
|
18727
|
+
createSessionResetOps(reply, perfSpan) {
|
|
18368
18728
|
return {
|
|
18369
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18729
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18370
18730
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18371
18731
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18372
18732
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
@@ -18381,10 +18741,10 @@ class CommandRouter {
|
|
|
18381
18741
|
getSession: (alias) => this.sessions.getSession(alias)
|
|
18382
18742
|
};
|
|
18383
18743
|
}
|
|
18384
|
-
createSessionShortcutOps(reply) {
|
|
18744
|
+
createSessionShortcutOps(reply, perfSpan) {
|
|
18385
18745
|
return {
|
|
18386
18746
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18387
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18747
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18388
18748
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18389
18749
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18390
18750
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
@@ -18445,13 +18805,14 @@ class CommandRouter {
|
|
|
18445
18805
|
throw error2;
|
|
18446
18806
|
}
|
|
18447
18807
|
}
|
|
18448
|
-
async ensureTransportSession(session, reply) {
|
|
18808
|
+
async ensureTransportSession(session, reply, perfSpan) {
|
|
18449
18809
|
const attemptSession = (operation) => {
|
|
18450
18810
|
const { handler, dispose } = this.createProgressHandler(session, reply);
|
|
18451
18811
|
return this.measureTransportCall(operation, session, () => this.transport.ensureSession(session, handler)).finally(dispose);
|
|
18452
18812
|
};
|
|
18453
18813
|
try {
|
|
18454
18814
|
await attemptSession("ensure_session");
|
|
18815
|
+
perfSpan?.mark("session.ready");
|
|
18455
18816
|
} catch (err) {
|
|
18456
18817
|
if (!(err instanceof MissingOptionalDepError))
|
|
18457
18818
|
throw err;
|
|
@@ -18464,6 +18825,7 @@ class CommandRouter {
|
|
|
18464
18825
|
await reply?.(`\uD83D\uDD04 安装完成,正在验证会话启动…`);
|
|
18465
18826
|
try {
|
|
18466
18827
|
await attemptSession("ensure_session.verify");
|
|
18828
|
+
perfSpan?.mark("session.ready");
|
|
18467
18829
|
return true;
|
|
18468
18830
|
} catch (retryErr) {
|
|
18469
18831
|
if (retryErr instanceof MissingOptionalDepError)
|
|
@@ -18529,11 +18891,13 @@ class CommandRouter {
|
|
|
18529
18891
|
async checkTransportSession(session) {
|
|
18530
18892
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
18531
18893
|
}
|
|
18532
|
-
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent) {
|
|
18894
|
+
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpan) {
|
|
18533
18895
|
session.mcpCoordinatorSession ??= session.transportSession;
|
|
18534
18896
|
let done = false;
|
|
18897
|
+
let abortRequested = false;
|
|
18535
18898
|
let cancelOnAbort;
|
|
18536
18899
|
const fireCancel = () => {
|
|
18900
|
+
abortRequested = true;
|
|
18537
18901
|
if (done)
|
|
18538
18902
|
return;
|
|
18539
18903
|
try {
|
|
@@ -18557,20 +18921,42 @@ class CommandRouter {
|
|
|
18557
18921
|
});
|
|
18558
18922
|
}
|
|
18559
18923
|
};
|
|
18924
|
+
let localOutcome = "ok";
|
|
18560
18925
|
if (abortSignal) {
|
|
18561
18926
|
if (abortSignal.aborted) {
|
|
18562
|
-
|
|
18563
|
-
|
|
18927
|
+
abortRequested = true;
|
|
18928
|
+
} else {
|
|
18929
|
+
cancelOnAbort = fireCancel;
|
|
18930
|
+
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18564
18931
|
}
|
|
18565
|
-
cancelOnAbort = fireCancel;
|
|
18566
|
-
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18567
18932
|
}
|
|
18933
|
+
let firstChunkFired = false;
|
|
18934
|
+
const onSegment = (_segment) => {
|
|
18935
|
+
if (!firstChunkFired) {
|
|
18936
|
+
firstChunkFired = true;
|
|
18937
|
+
perfSpan?.mark("transport.first_chunk");
|
|
18938
|
+
}
|
|
18939
|
+
};
|
|
18568
18940
|
try {
|
|
18941
|
+
if (abortRequested) {
|
|
18942
|
+
throw new DOMException("Aborted before prompt started", "AbortError");
|
|
18943
|
+
}
|
|
18944
|
+
perfSpan?.mark("transport.prompt_dispatched", {
|
|
18945
|
+
transportKind: this.config?.transport.type ?? inferTransportKind(this.transport)
|
|
18946
|
+
});
|
|
18569
18947
|
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
|
|
18570
18948
|
...media ? { media } : {},
|
|
18949
|
+
...reply ? { onSegment } : {},
|
|
18571
18950
|
...onToolEvent ? { onToolEvent } : {}
|
|
18572
18951
|
}));
|
|
18952
|
+
} catch (error2) {
|
|
18953
|
+
localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
|
|
18954
|
+
throw error2;
|
|
18573
18955
|
} finally {
|
|
18956
|
+
if (abortRequested && localOutcome === "ok") {
|
|
18957
|
+
localOutcome = "aborted";
|
|
18958
|
+
}
|
|
18959
|
+
perfSpan?.mark("transport.prompt_done", { localOutcome });
|
|
18574
18960
|
done = true;
|
|
18575
18961
|
if (cancelOnAbort && abortSignal) {
|
|
18576
18962
|
abortSignal.removeEventListener("abort", cancelOnAbort);
|
|
@@ -18631,6 +19017,12 @@ class CommandRouter {
|
|
|
18631
19017
|
}
|
|
18632
19018
|
}
|
|
18633
19019
|
}
|
|
19020
|
+
function isAbortError2(error2) {
|
|
19021
|
+
return error2 instanceof Error && error2.name === "AbortError";
|
|
19022
|
+
}
|
|
19023
|
+
function inferTransportKind(transport) {
|
|
19024
|
+
return transport.constructor.name.includes("Bridge") ? "acpx-bridge" : "acpx-cli";
|
|
19025
|
+
}
|
|
18634
19026
|
var init_command_router = __esm(() => {
|
|
18635
19027
|
init_app_logger();
|
|
18636
19028
|
init_acpx_session_index();
|
|
@@ -18720,7 +19112,8 @@ class ConsoleAgent {
|
|
|
18720
19112
|
mimeType: m.mimeType,
|
|
18721
19113
|
...m.fileName ? { fileName: m.fileName } : {}
|
|
18722
19114
|
})) : undefined;
|
|
18723
|
-
|
|
19115
|
+
request.perfSpan?.mark("agent.dispatched");
|
|
19116
|
+
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.perfSpan);
|
|
18724
19117
|
}
|
|
18725
19118
|
isKnownCommand(text) {
|
|
18726
19119
|
return isKnownWeacpxCommandText(text);
|
|
@@ -22738,12 +23131,21 @@ async function runConsole(paths, deps) {
|
|
|
22738
23131
|
runtimeForGc.orchestration.service.purgeExpiredResetCoordinators({ cutoffDays: 7, trigger: "interval" }).catch(() => {});
|
|
22739
23132
|
}, 86400000);
|
|
22740
23133
|
}
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
23134
|
+
try {
|
|
23135
|
+
await deps.channels.startAll({
|
|
23136
|
+
agent: runtime.agent,
|
|
23137
|
+
abortSignal: shutdownController.signal,
|
|
23138
|
+
quota: runtime.quota,
|
|
23139
|
+
logger: runtime.logger,
|
|
23140
|
+
perfTracer: runtime.perfTracer
|
|
23141
|
+
});
|
|
23142
|
+
} catch (error2) {
|
|
23143
|
+
if (deps.channelStartupPolicy !== "best-effort") {
|
|
23144
|
+
throw error2;
|
|
23145
|
+
}
|
|
23146
|
+
await runtime.logger.error("daemon.channels.start_failed", "all channels failed to start; daemon remains alive for orchestration IPC", { error: error2 instanceof Error ? error2.message : String(error2) });
|
|
23147
|
+
await waitForShutdown(shutdownController.signal);
|
|
23148
|
+
}
|
|
22747
23149
|
} finally {
|
|
22748
23150
|
await runCleanupSequence({
|
|
22749
23151
|
removeProcessListener,
|
|
@@ -22761,6 +23163,14 @@ async function runConsole(paths, deps) {
|
|
|
22761
23163
|
});
|
|
22762
23164
|
}
|
|
22763
23165
|
}
|
|
23166
|
+
async function waitForShutdown(signal) {
|
|
23167
|
+
if (signal.aborted) {
|
|
23168
|
+
return;
|
|
23169
|
+
}
|
|
23170
|
+
await new Promise((resolve3) => {
|
|
23171
|
+
signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
23172
|
+
});
|
|
23173
|
+
}
|
|
22764
23174
|
async function runCleanupSequence(input) {
|
|
22765
23175
|
let cleanupError = null;
|
|
22766
23176
|
input.removeProcessListener("SIGINT", input.signalHandler);
|
|
@@ -23782,12 +24192,12 @@ var init_streaming_prompt = __esm(() => {
|
|
|
23782
24192
|
|
|
23783
24193
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
23784
24194
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
23785
|
-
import { dirname as
|
|
24195
|
+
import { dirname as dirname12, join as join12 } from "node:path";
|
|
23786
24196
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
23787
24197
|
if (platform === "win32") {
|
|
23788
24198
|
return null;
|
|
23789
24199
|
}
|
|
23790
|
-
return join12(
|
|
24200
|
+
return join12(dirname12(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
23791
24201
|
}
|
|
23792
24202
|
async function ensureNodePtyHelperExecutable(helperPath, chmod2 = chmodFs) {
|
|
23793
24203
|
if (!helperPath) {
|
|
@@ -24622,7 +25032,7 @@ __export(exports_main, {
|
|
|
24622
25032
|
});
|
|
24623
25033
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24624
25034
|
import { homedir as homedir9 } from "node:os";
|
|
24625
|
-
import { dirname as
|
|
25035
|
+
import { dirname as dirname13, join as join14 } from "node:path";
|
|
24626
25036
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
24627
25037
|
function startProgressHeartbeat(orchestration, config2, logger2, channel) {
|
|
24628
25038
|
const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
|
|
@@ -24669,6 +25079,15 @@ async function buildApp(paths, deps = {}) {
|
|
|
24669
25079
|
now: deps.loggerNow
|
|
24670
25080
|
});
|
|
24671
25081
|
await logger2.cleanup();
|
|
25082
|
+
const perfLogPath = paths.perfLogPath ?? resolvePerfLogPath(paths.configPath);
|
|
25083
|
+
const perfTracer = config2.logging.perf.enabled ? createPerfTracer({
|
|
25084
|
+
filePath: perfLogPath,
|
|
25085
|
+
maxSizeBytes: config2.logging.perf.maxSizeBytes,
|
|
25086
|
+
maxFiles: config2.logging.perf.maxFiles,
|
|
25087
|
+
retentionDays: config2.logging.perf.retentionDays,
|
|
25088
|
+
appLogger: logger2
|
|
25089
|
+
}) : createNoopPerfTracer();
|
|
25090
|
+
await perfTracer.cleanup();
|
|
24672
25091
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config2.transport.command });
|
|
24673
25092
|
const stateStore = new StateStore(paths.statePath);
|
|
24674
25093
|
const state = await stateStore.load();
|
|
@@ -25027,6 +25446,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
25027
25446
|
stateStore,
|
|
25028
25447
|
configStore,
|
|
25029
25448
|
logger: logger2,
|
|
25449
|
+
perfTracer,
|
|
25030
25450
|
quota,
|
|
25031
25451
|
transport,
|
|
25032
25452
|
orchestration: {
|
|
@@ -25043,6 +25463,13 @@ async function buildApp(paths, deps = {}) {
|
|
|
25043
25463
|
if ("dispose" in transport && typeof transport.dispose === "function") {
|
|
25044
25464
|
await transport.dispose();
|
|
25045
25465
|
}
|
|
25466
|
+
try {
|
|
25467
|
+
await perfTracer.flush();
|
|
25468
|
+
} catch (err) {
|
|
25469
|
+
await logger2.error("perf.flush_failed", "perf tracer flush failed during shutdown", {
|
|
25470
|
+
error: err instanceof Error ? err.message : String(err)
|
|
25471
|
+
}).catch(() => {});
|
|
25472
|
+
}
|
|
25046
25473
|
await logger2.flush();
|
|
25047
25474
|
}
|
|
25048
25475
|
};
|
|
@@ -25078,7 +25505,7 @@ async function main() {
|
|
|
25078
25505
|
}
|
|
25079
25506
|
}
|
|
25080
25507
|
async function prepareChannelMedia(configPath, config2) {
|
|
25081
|
-
const runtimeDir = join14(
|
|
25508
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25082
25509
|
const mediaRootDir = join14(runtimeDir, "media");
|
|
25083
25510
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
25084
25511
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -25093,10 +25520,11 @@ function resolveRuntimePaths() {
|
|
|
25093
25520
|
throw new Error("Unable to resolve the current user home directory");
|
|
25094
25521
|
}
|
|
25095
25522
|
const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
|
|
25096
|
-
const runtimeDir = join14(
|
|
25523
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25097
25524
|
return {
|
|
25098
25525
|
configPath,
|
|
25099
25526
|
statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
|
|
25527
|
+
perfLogPath: join14(runtimeDir, "perf.log"),
|
|
25100
25528
|
orchestrationSocketPath: process.env.WEACPX_ORCHESTRATION_SOCKET ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
|
|
25101
25529
|
};
|
|
25102
25530
|
}
|
|
@@ -25107,10 +25535,15 @@ function resolveBridgeEntryPath() {
|
|
|
25107
25535
|
return fileURLToPath4(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
25108
25536
|
}
|
|
25109
25537
|
function resolveAppLogPath(configPath) {
|
|
25110
|
-
const rootDir =
|
|
25538
|
+
const rootDir = dirname13(configPath);
|
|
25111
25539
|
const runtimeDir = join14(rootDir, "runtime");
|
|
25112
25540
|
return join14(runtimeDir, "app.log");
|
|
25113
25541
|
}
|
|
25542
|
+
function resolvePerfLogPath(configPath) {
|
|
25543
|
+
const rootDir = dirname13(configPath);
|
|
25544
|
+
const runtimeDir = join14(rootDir, "runtime");
|
|
25545
|
+
return join14(runtimeDir, "perf.log");
|
|
25546
|
+
}
|
|
25114
25547
|
function resolveOrchestrationSocketPathFromConfigPath(configPath) {
|
|
25115
25548
|
const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
|
|
25116
25549
|
return resolveDaemonOrchestrationSocketPath(runtimeDir);
|
|
@@ -25141,6 +25574,7 @@ var init_main = __esm(async () => {
|
|
|
25141
25574
|
init_media_store();
|
|
25142
25575
|
init_quota_errors();
|
|
25143
25576
|
init_inbound();
|
|
25577
|
+
init_perf_tracer();
|
|
25144
25578
|
init_bootstrap();
|
|
25145
25579
|
if (false) {}
|
|
25146
25580
|
});
|
|
@@ -25483,7 +25917,7 @@ async function checkOrchestrationHealth(options) {
|
|
|
25483
25917
|
// src/doctor/checks/runtime-check.ts
|
|
25484
25918
|
import { constants } from "node:fs";
|
|
25485
25919
|
import { access as access4, stat as stat3 } from "node:fs/promises";
|
|
25486
|
-
import { dirname as
|
|
25920
|
+
import { dirname as dirname14 } from "node:path";
|
|
25487
25921
|
import { homedir as homedir11 } from "node:os";
|
|
25488
25922
|
async function checkRuntime(options = {}) {
|
|
25489
25923
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
@@ -25580,7 +26014,7 @@ async function checkFileCreatable(label, path14, probe, platform) {
|
|
|
25580
26014
|
detail: `${label}: ${path14} (unusable: ${formatError6(error2)})`
|
|
25581
26015
|
};
|
|
25582
26016
|
}
|
|
25583
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
26017
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname14(path14), probe, platform);
|
|
25584
26018
|
if (!parentCheck.ok) {
|
|
25585
26019
|
return {
|
|
25586
26020
|
ok: false,
|
|
@@ -25616,7 +26050,7 @@ async function checkCreatableAncestorDirectory(path14, probe, platform) {
|
|
|
25616
26050
|
blockingPath: path14
|
|
25617
26051
|
};
|
|
25618
26052
|
}
|
|
25619
|
-
const parent =
|
|
26053
|
+
const parent = dirname14(path14);
|
|
25620
26054
|
if (parent === path14) {
|
|
25621
26055
|
return {
|
|
25622
26056
|
ok: false,
|
|
@@ -26198,7 +26632,7 @@ init_create_daemon_controller();
|
|
|
26198
26632
|
init_daemon_files();
|
|
26199
26633
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26200
26634
|
import { homedir as homedir13 } from "node:os";
|
|
26201
|
-
import { dirname as
|
|
26635
|
+
import { dirname as dirname15, join as join16, sep } from "node:path";
|
|
26202
26636
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
26203
26637
|
|
|
26204
26638
|
// src/daemon/daemon-runtime.ts
|
|
@@ -39743,7 +40177,7 @@ function sanitizeName(input, fallback) {
|
|
|
39743
40177
|
init_plugin_home();
|
|
39744
40178
|
import { spawn as spawn4 } from "node:child_process";
|
|
39745
40179
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
39746
|
-
import { dirname as
|
|
40180
|
+
import { dirname as dirname9, join as join7 } from "node:path";
|
|
39747
40181
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
39748
40182
|
|
|
39749
40183
|
// src/plugins/package-manager.ts
|
|
@@ -40021,8 +40455,8 @@ async function runInherit(command, args) {
|
|
|
40021
40455
|
}
|
|
40022
40456
|
async function readPackageName() {
|
|
40023
40457
|
try {
|
|
40024
|
-
const here =
|
|
40025
|
-
for (const candidate of [
|
|
40458
|
+
const here = dirname9(fileURLToPath2(import.meta.url));
|
|
40459
|
+
for (const candidate of [join7(here, "..", "package.json"), join7(here, "..", "..", "package.json")]) {
|
|
40026
40460
|
try {
|
|
40027
40461
|
const parsed = JSON.parse(await readFile7(candidate, "utf8"));
|
|
40028
40462
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
@@ -40648,7 +41082,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
40648
41082
|
// src/plugins/plugin-cli.ts
|
|
40649
41083
|
init_plugin_home();
|
|
40650
41084
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
40651
|
-
import { isAbsolute, join as
|
|
41085
|
+
import { isAbsolute, join as join9, resolve } from "node:path";
|
|
40652
41086
|
init_plugin_loader();
|
|
40653
41087
|
init_validate_plugin();
|
|
40654
41088
|
|
|
@@ -40658,13 +41092,13 @@ init_plugin_loader();
|
|
|
40658
41092
|
init_validate_plugin();
|
|
40659
41093
|
init_known_plugins();
|
|
40660
41094
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
40661
|
-
import { join as
|
|
41095
|
+
import { join as join8 } from "node:path";
|
|
40662
41096
|
function suggestedPluginPackageForChannel(type) {
|
|
40663
41097
|
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
40664
41098
|
}
|
|
40665
41099
|
async function readDependencyEntries(pluginHome) {
|
|
40666
41100
|
try {
|
|
40667
|
-
const raw = await readFile8(
|
|
41101
|
+
const raw = await readFile8(join8(pluginHome, "package.json"), "utf8");
|
|
40668
41102
|
const parsed = JSON.parse(raw);
|
|
40669
41103
|
const out = {};
|
|
40670
41104
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40766,7 +41200,7 @@ function looksLikePath(spec) {
|
|
|
40766
41200
|
}
|
|
40767
41201
|
async function readDependencyEntries2(pluginHome) {
|
|
40768
41202
|
try {
|
|
40769
|
-
const raw = await readFile9(
|
|
41203
|
+
const raw = await readFile9(join9(pluginHome, "package.json"), "utf8");
|
|
40770
41204
|
const parsed = JSON.parse(raw);
|
|
40771
41205
|
const out = {};
|
|
40772
41206
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40792,7 +41226,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
40792
41226
|
return name;
|
|
40793
41227
|
}
|
|
40794
41228
|
try {
|
|
40795
|
-
const raw = await readFile9(
|
|
41229
|
+
const raw = await readFile9(join9(installSpec, "package.json"), "utf8");
|
|
40796
41230
|
const parsed = JSON.parse(raw);
|
|
40797
41231
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
40798
41232
|
return parsed.name.trim();
|
|
@@ -41741,6 +42175,7 @@ async function defaultLoadConfiguredPluginsForChannelCli() {
|
|
|
41741
42175
|
const { loadConfiguredPlugins: loadConfiguredPlugins2 } = await Promise.resolve().then(() => (init_plugin_loader(), exports_plugin_loader));
|
|
41742
42176
|
await loadConfiguredPlugins2({ plugins: config2.plugins });
|
|
41743
42177
|
}
|
|
42178
|
+
var DAEMON_RUN_ENV = "WEACPX_DAEMON_RUN";
|
|
41744
42179
|
async function defaultRun(options = {}) {
|
|
41745
42180
|
const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2, prepareChannelMedia: prepareChannelMedia2 }, { runConsole: runConsole2 }] = await Promise.all([
|
|
41746
42181
|
init_main().then(() => exports_main),
|
|
@@ -41769,6 +42204,7 @@ async function defaultRun(options = {}) {
|
|
|
41769
42204
|
await createFirstRunSession(runtime, firstRunOnboarding);
|
|
41770
42205
|
} : undefined,
|
|
41771
42206
|
channels: channelRegistry,
|
|
42207
|
+
channelStartupPolicy: process.env[DAEMON_RUN_ENV] === "1" ? "best-effort" : "require-one",
|
|
41772
42208
|
daemonRuntime,
|
|
41773
42209
|
...firstLockCreator ? {
|
|
41774
42210
|
consumerLockFactory: (runtime) => firstLockCreator.create({
|
|
@@ -42063,7 +42499,7 @@ function safeDaemonLogPaths() {
|
|
|
42063
42499
|
const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
42064
42500
|
const paths = resolveDaemonPaths({ home: requireHome2() });
|
|
42065
42501
|
return {
|
|
42066
|
-
appLog: join16(
|
|
42502
|
+
appLog: join16(dirname15(configPath), "runtime", "app.log"),
|
|
42067
42503
|
stderrLog: paths.stderrLog
|
|
42068
42504
|
};
|
|
42069
42505
|
} catch {
|