weacpx 0.4.2 → 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 +883 -378
- package/dist/config/types.d.ts +7 -0
- package/dist/logging/rotating-file-writer.d.ts +2 -0
- package/dist/orchestration/orchestration-types.d.ts +1 -1
- 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 `",
|
|
@@ -9555,7 +9602,7 @@ function isOptionalBoolean(value) {
|
|
|
9555
9602
|
return value === undefined || typeof value === "boolean";
|
|
9556
9603
|
}
|
|
9557
9604
|
function isTaskStatus(value) {
|
|
9558
|
-
return value === "
|
|
9605
|
+
return value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
|
|
9559
9606
|
}
|
|
9560
9607
|
function isSourceKind(value) {
|
|
9561
9608
|
return value === "human" || value === "coordinator" || value === "worker";
|
|
@@ -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,
|
|
@@ -18204,6 +18562,8 @@ class CommandRouter {
|
|
|
18204
18562
|
});
|
|
18205
18563
|
return { text: renderCommandAccessDenied(command) };
|
|
18206
18564
|
}
|
|
18565
|
+
await this.refreshConfigFromStore();
|
|
18566
|
+
perfSpan?.mark("router.config_refreshed");
|
|
18207
18567
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
18208
18568
|
switch (command.kind) {
|
|
18209
18569
|
case "invalid":
|
|
@@ -18246,35 +18606,35 @@ class CommandRouter {
|
|
|
18246
18606
|
case "workspace.rm":
|
|
18247
18607
|
return await handleWorkspaceRemove(this.createHandlerContext(), command.name);
|
|
18248
18608
|
case "sessions":
|
|
18249
|
-
return await handleSessions(this.createSessionHandlerContext(), chatKey);
|
|
18609
|
+
return await handleSessions(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18250
18610
|
case "session.new":
|
|
18251
|
-
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);
|
|
18252
18612
|
case "session.shortcut":
|
|
18253
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, false);
|
|
18613
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, false);
|
|
18254
18614
|
case "session.shortcut.new":
|
|
18255
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, true);
|
|
18615
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
|
|
18256
18616
|
case "session.attach":
|
|
18257
|
-
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);
|
|
18258
18618
|
case "session.use":
|
|
18259
|
-
return await handleSessionUse(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18619
|
+
return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18260
18620
|
case "mode.show":
|
|
18261
|
-
return await handleModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18621
|
+
return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18262
18622
|
case "mode.set":
|
|
18263
|
-
return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
|
|
18623
|
+
return await handleModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.modeId);
|
|
18264
18624
|
case "replymode.show":
|
|
18265
|
-
return await handleReplyModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18625
|
+
return await handleReplyModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18266
18626
|
case "replymode.set":
|
|
18267
|
-
return await handleReplyModeSet(this.createSessionHandlerContext(), chatKey, command.replyMode);
|
|
18627
|
+
return await handleReplyModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.replyMode);
|
|
18268
18628
|
case "replymode.reset":
|
|
18269
|
-
return await handleReplyModeReset(this.createSessionHandlerContext(), chatKey);
|
|
18629
|
+
return await handleReplyModeReset(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18270
18630
|
case "status":
|
|
18271
|
-
return await handleStatus(this.createSessionHandlerContext(), chatKey);
|
|
18631
|
+
return await handleStatus(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18272
18632
|
case "cancel":
|
|
18273
|
-
return await handleCancel(this.createSessionHandlerContext(), chatKey);
|
|
18633
|
+
return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18274
18634
|
case "session.reset":
|
|
18275
|
-
return await handleSessionReset(this.createSessionHandlerContext(reply), chatKey);
|
|
18635
|
+
return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
|
|
18276
18636
|
case "session.rm":
|
|
18277
|
-
return await handleSessionRemove(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18637
|
+
return await handleSessionRemove(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18278
18638
|
case "groups":
|
|
18279
18639
|
return await handleGroupList(this.createHandlerContext(), chatKey, command.filter);
|
|
18280
18640
|
case "group.new":
|
|
@@ -18300,7 +18660,7 @@ class CommandRouter {
|
|
|
18300
18660
|
case "task.cancel":
|
|
18301
18661
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
18302
18662
|
case "prompt":
|
|
18303
|
-
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);
|
|
18304
18664
|
}
|
|
18305
18665
|
});
|
|
18306
18666
|
}
|
|
@@ -18319,23 +18679,24 @@ class CommandRouter {
|
|
|
18319
18679
|
...this.quota ? { quota: this.quota } : {}
|
|
18320
18680
|
};
|
|
18321
18681
|
}
|
|
18322
|
-
createSessionHandlerContext(reply) {
|
|
18682
|
+
createSessionHandlerContext(reply, perfSpan) {
|
|
18323
18683
|
return {
|
|
18324
18684
|
...this.createHandlerContext(),
|
|
18325
|
-
lifecycle: this.createSessionLifecycleOps(reply),
|
|
18326
|
-
interaction: this.createSessionInteractionOps(),
|
|
18685
|
+
lifecycle: this.createSessionLifecycleOps(reply, perfSpan),
|
|
18686
|
+
interaction: this.createSessionInteractionOps(perfSpan),
|
|
18327
18687
|
recovery: this.createSessionRenderRecoveryOps()
|
|
18328
18688
|
};
|
|
18329
18689
|
}
|
|
18330
|
-
createSessionLifecycleOps(reply) {
|
|
18690
|
+
createSessionLifecycleOps(reply, perfSpan) {
|
|
18331
18691
|
return {
|
|
18332
18692
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18333
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18693
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18334
18694
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18695
|
+
markSessionReady: () => perfSpan?.mark("session.ready"),
|
|
18335
18696
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18336
18697
|
handleSessionShortcut: async (chatKey, agent, target, createNew, replyOverride) => {
|
|
18337
18698
|
try {
|
|
18338
|
-
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);
|
|
18339
18700
|
} catch (err) {
|
|
18340
18701
|
if (err instanceof AutoInstallFailedError) {
|
|
18341
18702
|
const session = this.sessions.resolveSession(`${agent}`, agent, target.workspace ?? "", `${agent}`);
|
|
@@ -18344,15 +18705,15 @@ class CommandRouter {
|
|
|
18344
18705
|
throw err;
|
|
18345
18706
|
}
|
|
18346
18707
|
},
|
|
18347
|
-
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply), chatKey),
|
|
18708
|
+
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply, perfSpan), chatKey),
|
|
18348
18709
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
18349
18710
|
};
|
|
18350
18711
|
}
|
|
18351
|
-
createSessionInteractionOps() {
|
|
18712
|
+
createSessionInteractionOps(perfSpan) {
|
|
18352
18713
|
return {
|
|
18353
18714
|
setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
|
|
18354
18715
|
cancelTransportSession: (session) => this.cancelTransportSession(session),
|
|
18355
|
-
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)
|
|
18356
18717
|
};
|
|
18357
18718
|
}
|
|
18358
18719
|
createSessionRenderRecoveryOps() {
|
|
@@ -18363,9 +18724,9 @@ class CommandRouter {
|
|
|
18363
18724
|
renderTransportError: (session, error2) => renderTransportError(session, error2)
|
|
18364
18725
|
};
|
|
18365
18726
|
}
|
|
18366
|
-
createSessionResetOps(reply) {
|
|
18727
|
+
createSessionResetOps(reply, perfSpan) {
|
|
18367
18728
|
return {
|
|
18368
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18729
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18369
18730
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18370
18731
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18371
18732
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
@@ -18380,10 +18741,10 @@ class CommandRouter {
|
|
|
18380
18741
|
getSession: (alias) => this.sessions.getSession(alias)
|
|
18381
18742
|
};
|
|
18382
18743
|
}
|
|
18383
|
-
createSessionShortcutOps(reply) {
|
|
18744
|
+
createSessionShortcutOps(reply, perfSpan) {
|
|
18384
18745
|
return {
|
|
18385
18746
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18386
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18747
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18387
18748
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18388
18749
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18389
18750
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
@@ -18409,8 +18770,21 @@ class CommandRouter {
|
|
|
18409
18770
|
...channel,
|
|
18410
18771
|
...channel.options ? { options: { ...channel.options } } : {}
|
|
18411
18772
|
}));
|
|
18773
|
+
this.config.plugins = updated.plugins.map((plugin) => ({ ...plugin }));
|
|
18412
18774
|
this.config.agents = { ...updated.agents };
|
|
18413
18775
|
this.config.workspaces = { ...updated.workspaces };
|
|
18776
|
+
this.config.orchestration = {
|
|
18777
|
+
...updated.orchestration,
|
|
18778
|
+
allowedAgentRequestTargets: [...updated.orchestration.allowedAgentRequestTargets],
|
|
18779
|
+
allowedAgentRequestRoles: [...updated.orchestration.allowedAgentRequestRoles]
|
|
18780
|
+
};
|
|
18781
|
+
}
|
|
18782
|
+
async refreshConfigFromStore() {
|
|
18783
|
+
if (!this.config || !this.configStore) {
|
|
18784
|
+
return;
|
|
18785
|
+
}
|
|
18786
|
+
const updated = await this.configStore.load();
|
|
18787
|
+
this.replaceConfig(updated);
|
|
18414
18788
|
}
|
|
18415
18789
|
async executeCommand(chatKey, kind, startedAt, operation) {
|
|
18416
18790
|
try {
|
|
@@ -18431,13 +18805,14 @@ class CommandRouter {
|
|
|
18431
18805
|
throw error2;
|
|
18432
18806
|
}
|
|
18433
18807
|
}
|
|
18434
|
-
async ensureTransportSession(session, reply) {
|
|
18808
|
+
async ensureTransportSession(session, reply, perfSpan) {
|
|
18435
18809
|
const attemptSession = (operation) => {
|
|
18436
18810
|
const { handler, dispose } = this.createProgressHandler(session, reply);
|
|
18437
18811
|
return this.measureTransportCall(operation, session, () => this.transport.ensureSession(session, handler)).finally(dispose);
|
|
18438
18812
|
};
|
|
18439
18813
|
try {
|
|
18440
18814
|
await attemptSession("ensure_session");
|
|
18815
|
+
perfSpan?.mark("session.ready");
|
|
18441
18816
|
} catch (err) {
|
|
18442
18817
|
if (!(err instanceof MissingOptionalDepError))
|
|
18443
18818
|
throw err;
|
|
@@ -18450,6 +18825,7 @@ class CommandRouter {
|
|
|
18450
18825
|
await reply?.(`\uD83D\uDD04 安装完成,正在验证会话启动…`);
|
|
18451
18826
|
try {
|
|
18452
18827
|
await attemptSession("ensure_session.verify");
|
|
18828
|
+
perfSpan?.mark("session.ready");
|
|
18453
18829
|
return true;
|
|
18454
18830
|
} catch (retryErr) {
|
|
18455
18831
|
if (retryErr instanceof MissingOptionalDepError)
|
|
@@ -18515,11 +18891,13 @@ class CommandRouter {
|
|
|
18515
18891
|
async checkTransportSession(session) {
|
|
18516
18892
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
18517
18893
|
}
|
|
18518
|
-
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent) {
|
|
18894
|
+
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpan) {
|
|
18519
18895
|
session.mcpCoordinatorSession ??= session.transportSession;
|
|
18520
18896
|
let done = false;
|
|
18897
|
+
let abortRequested = false;
|
|
18521
18898
|
let cancelOnAbort;
|
|
18522
18899
|
const fireCancel = () => {
|
|
18900
|
+
abortRequested = true;
|
|
18523
18901
|
if (done)
|
|
18524
18902
|
return;
|
|
18525
18903
|
try {
|
|
@@ -18543,20 +18921,42 @@ class CommandRouter {
|
|
|
18543
18921
|
});
|
|
18544
18922
|
}
|
|
18545
18923
|
};
|
|
18924
|
+
let localOutcome = "ok";
|
|
18546
18925
|
if (abortSignal) {
|
|
18547
18926
|
if (abortSignal.aborted) {
|
|
18548
|
-
|
|
18549
|
-
|
|
18927
|
+
abortRequested = true;
|
|
18928
|
+
} else {
|
|
18929
|
+
cancelOnAbort = fireCancel;
|
|
18930
|
+
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18550
18931
|
}
|
|
18551
|
-
cancelOnAbort = fireCancel;
|
|
18552
|
-
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18553
18932
|
}
|
|
18933
|
+
let firstChunkFired = false;
|
|
18934
|
+
const onSegment = (_segment) => {
|
|
18935
|
+
if (!firstChunkFired) {
|
|
18936
|
+
firstChunkFired = true;
|
|
18937
|
+
perfSpan?.mark("transport.first_chunk");
|
|
18938
|
+
}
|
|
18939
|
+
};
|
|
18554
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
|
+
});
|
|
18555
18947
|
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
|
|
18556
18948
|
...media ? { media } : {},
|
|
18949
|
+
...reply ? { onSegment } : {},
|
|
18557
18950
|
...onToolEvent ? { onToolEvent } : {}
|
|
18558
18951
|
}));
|
|
18952
|
+
} catch (error2) {
|
|
18953
|
+
localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
|
|
18954
|
+
throw error2;
|
|
18559
18955
|
} finally {
|
|
18956
|
+
if (abortRequested && localOutcome === "ok") {
|
|
18957
|
+
localOutcome = "aborted";
|
|
18958
|
+
}
|
|
18959
|
+
perfSpan?.mark("transport.prompt_done", { localOutcome });
|
|
18560
18960
|
done = true;
|
|
18561
18961
|
if (cancelOnAbort && abortSignal) {
|
|
18562
18962
|
abortSignal.removeEventListener("abort", cancelOnAbort);
|
|
@@ -18617,6 +19017,12 @@ class CommandRouter {
|
|
|
18617
19017
|
}
|
|
18618
19018
|
}
|
|
18619
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
|
+
}
|
|
18620
19026
|
var init_command_router = __esm(() => {
|
|
18621
19027
|
init_app_logger();
|
|
18622
19028
|
init_acpx_session_index();
|
|
@@ -18706,7 +19112,8 @@ class ConsoleAgent {
|
|
|
18706
19112
|
mimeType: m.mimeType,
|
|
18707
19113
|
...m.fileName ? { fileName: m.fileName } : {}
|
|
18708
19114
|
})) : undefined;
|
|
18709
|
-
|
|
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);
|
|
18710
19117
|
}
|
|
18711
19118
|
isKnownCommand(text) {
|
|
18712
19119
|
return isKnownWeacpxCommandText(text);
|
|
@@ -18984,7 +19391,6 @@ class OrchestrationServer {
|
|
|
18984
19391
|
}
|
|
18985
19392
|
requireOnlyKeys(filter, ["coordinatorSession", "status", "stuck", "sort", "order"], "filter");
|
|
18986
19393
|
const status = requireOptionalEnum(filter, "status", [
|
|
18987
|
-
"pending",
|
|
18988
19394
|
"needs_confirmation",
|
|
18989
19395
|
"running",
|
|
18990
19396
|
"blocked",
|
|
@@ -21381,7 +21787,7 @@ class OrchestrationService {
|
|
|
21381
21787
|
}
|
|
21382
21788
|
buildGroupSummary(group, tasks) {
|
|
21383
21789
|
const sortedTasks = tasks.slice().sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((task) => ({ ...task }));
|
|
21384
|
-
const pendingApprovalTasks = sortedTasks.filter((task) => task.status === "
|
|
21790
|
+
const pendingApprovalTasks = sortedTasks.filter((task) => task.status === "needs_confirmation").length;
|
|
21385
21791
|
const runningTasks = sortedTasks.filter((task) => task.status === "running").length;
|
|
21386
21792
|
const completedTasks = sortedTasks.filter((task) => task.status === "completed").length;
|
|
21387
21793
|
const failedTasks = sortedTasks.filter((task) => task.status === "failed").length;
|
|
@@ -22236,7 +22642,7 @@ function isTerminalTaskStatus2(status) {
|
|
|
22236
22642
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
22237
22643
|
}
|
|
22238
22644
|
function isAttentionRequiredTask(task) {
|
|
22239
|
-
return task.reviewPending !== undefined || task.status === "
|
|
22645
|
+
return task.reviewPending !== undefined || task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human";
|
|
22240
22646
|
}
|
|
22241
22647
|
function clampWaitTimeout(timeoutMs) {
|
|
22242
22648
|
if (timeoutMs === undefined) {
|
|
@@ -22725,12 +23131,21 @@ async function runConsole(paths, deps) {
|
|
|
22725
23131
|
runtimeForGc.orchestration.service.purgeExpiredResetCoordinators({ cutoffDays: 7, trigger: "interval" }).catch(() => {});
|
|
22726
23132
|
}, 86400000);
|
|
22727
23133
|
}
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
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
|
+
}
|
|
22734
23149
|
} finally {
|
|
22735
23150
|
await runCleanupSequence({
|
|
22736
23151
|
removeProcessListener,
|
|
@@ -22748,6 +23163,14 @@ async function runConsole(paths, deps) {
|
|
|
22748
23163
|
});
|
|
22749
23164
|
}
|
|
22750
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
|
+
}
|
|
22751
23174
|
async function runCleanupSequence(input) {
|
|
22752
23175
|
let cleanupError = null;
|
|
22753
23176
|
input.removeProcessListener("SIGINT", input.signalHandler);
|
|
@@ -23769,12 +24192,12 @@ var init_streaming_prompt = __esm(() => {
|
|
|
23769
24192
|
|
|
23770
24193
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
23771
24194
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
23772
|
-
import { dirname as
|
|
24195
|
+
import { dirname as dirname12, join as join12 } from "node:path";
|
|
23773
24196
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
23774
24197
|
if (platform === "win32") {
|
|
23775
24198
|
return null;
|
|
23776
24199
|
}
|
|
23777
|
-
return join12(
|
|
24200
|
+
return join12(dirname12(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
23778
24201
|
}
|
|
23779
24202
|
async function ensureNodePtyHelperExecutable(helperPath, chmod2 = chmodFs) {
|
|
23780
24203
|
if (!helperPath) {
|
|
@@ -24609,7 +25032,7 @@ __export(exports_main, {
|
|
|
24609
25032
|
});
|
|
24610
25033
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24611
25034
|
import { homedir as homedir9 } from "node:os";
|
|
24612
|
-
import { dirname as
|
|
25035
|
+
import { dirname as dirname13, join as join14 } from "node:path";
|
|
24613
25036
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
24614
25037
|
function startProgressHeartbeat(orchestration, config2, logger2, channel) {
|
|
24615
25038
|
const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
|
|
@@ -24656,6 +25079,15 @@ async function buildApp(paths, deps = {}) {
|
|
|
24656
25079
|
now: deps.loggerNow
|
|
24657
25080
|
});
|
|
24658
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();
|
|
24659
25091
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config2.transport.command });
|
|
24660
25092
|
const stateStore = new StateStore(paths.statePath);
|
|
24661
25093
|
const state = await stateStore.load();
|
|
@@ -25014,6 +25446,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
25014
25446
|
stateStore,
|
|
25015
25447
|
configStore,
|
|
25016
25448
|
logger: logger2,
|
|
25449
|
+
perfTracer,
|
|
25017
25450
|
quota,
|
|
25018
25451
|
transport,
|
|
25019
25452
|
orchestration: {
|
|
@@ -25030,6 +25463,13 @@ async function buildApp(paths, deps = {}) {
|
|
|
25030
25463
|
if ("dispose" in transport && typeof transport.dispose === "function") {
|
|
25031
25464
|
await transport.dispose();
|
|
25032
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
|
+
}
|
|
25033
25473
|
await logger2.flush();
|
|
25034
25474
|
}
|
|
25035
25475
|
};
|
|
@@ -25065,7 +25505,7 @@ async function main() {
|
|
|
25065
25505
|
}
|
|
25066
25506
|
}
|
|
25067
25507
|
async function prepareChannelMedia(configPath, config2) {
|
|
25068
|
-
const runtimeDir = join14(
|
|
25508
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25069
25509
|
const mediaRootDir = join14(runtimeDir, "media");
|
|
25070
25510
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
25071
25511
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -25080,10 +25520,11 @@ function resolveRuntimePaths() {
|
|
|
25080
25520
|
throw new Error("Unable to resolve the current user home directory");
|
|
25081
25521
|
}
|
|
25082
25522
|
const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
|
|
25083
|
-
const runtimeDir = join14(
|
|
25523
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25084
25524
|
return {
|
|
25085
25525
|
configPath,
|
|
25086
25526
|
statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
|
|
25527
|
+
perfLogPath: join14(runtimeDir, "perf.log"),
|
|
25087
25528
|
orchestrationSocketPath: process.env.WEACPX_ORCHESTRATION_SOCKET ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
|
|
25088
25529
|
};
|
|
25089
25530
|
}
|
|
@@ -25094,10 +25535,15 @@ function resolveBridgeEntryPath() {
|
|
|
25094
25535
|
return fileURLToPath4(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
25095
25536
|
}
|
|
25096
25537
|
function resolveAppLogPath(configPath) {
|
|
25097
|
-
const rootDir =
|
|
25538
|
+
const rootDir = dirname13(configPath);
|
|
25098
25539
|
const runtimeDir = join14(rootDir, "runtime");
|
|
25099
25540
|
return join14(runtimeDir, "app.log");
|
|
25100
25541
|
}
|
|
25542
|
+
function resolvePerfLogPath(configPath) {
|
|
25543
|
+
const rootDir = dirname13(configPath);
|
|
25544
|
+
const runtimeDir = join14(rootDir, "runtime");
|
|
25545
|
+
return join14(runtimeDir, "perf.log");
|
|
25546
|
+
}
|
|
25101
25547
|
function resolveOrchestrationSocketPathFromConfigPath(configPath) {
|
|
25102
25548
|
const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
|
|
25103
25549
|
return resolveDaemonOrchestrationSocketPath(runtimeDir);
|
|
@@ -25128,6 +25574,7 @@ var init_main = __esm(async () => {
|
|
|
25128
25574
|
init_media_store();
|
|
25129
25575
|
init_quota_errors();
|
|
25130
25576
|
init_inbound();
|
|
25577
|
+
init_perf_tracer();
|
|
25131
25578
|
init_bootstrap();
|
|
25132
25579
|
if (false) {}
|
|
25133
25580
|
});
|
|
@@ -25470,7 +25917,7 @@ async function checkOrchestrationHealth(options) {
|
|
|
25470
25917
|
// src/doctor/checks/runtime-check.ts
|
|
25471
25918
|
import { constants } from "node:fs";
|
|
25472
25919
|
import { access as access4, stat as stat3 } from "node:fs/promises";
|
|
25473
|
-
import { dirname as
|
|
25920
|
+
import { dirname as dirname14 } from "node:path";
|
|
25474
25921
|
import { homedir as homedir11 } from "node:os";
|
|
25475
25922
|
async function checkRuntime(options = {}) {
|
|
25476
25923
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
@@ -25567,7 +26014,7 @@ async function checkFileCreatable(label, path14, probe, platform) {
|
|
|
25567
26014
|
detail: `${label}: ${path14} (unusable: ${formatError6(error2)})`
|
|
25568
26015
|
};
|
|
25569
26016
|
}
|
|
25570
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
26017
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname14(path14), probe, platform);
|
|
25571
26018
|
if (!parentCheck.ok) {
|
|
25572
26019
|
return {
|
|
25573
26020
|
ok: false,
|
|
@@ -25603,7 +26050,7 @@ async function checkCreatableAncestorDirectory(path14, probe, platform) {
|
|
|
25603
26050
|
blockingPath: path14
|
|
25604
26051
|
};
|
|
25605
26052
|
}
|
|
25606
|
-
const parent =
|
|
26053
|
+
const parent = dirname14(path14);
|
|
25607
26054
|
if (parent === path14) {
|
|
25608
26055
|
return {
|
|
25609
26056
|
ok: false,
|
|
@@ -26185,7 +26632,7 @@ init_create_daemon_controller();
|
|
|
26185
26632
|
init_daemon_files();
|
|
26186
26633
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26187
26634
|
import { homedir as homedir13 } from "node:os";
|
|
26188
|
-
import { dirname as
|
|
26635
|
+
import { dirname as dirname15, join as join16, sep } from "node:path";
|
|
26189
26636
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
26190
26637
|
|
|
26191
26638
|
// src/daemon/daemon-runtime.ts
|
|
@@ -38624,7 +39071,6 @@ init_task_wait_timeouts();
|
|
|
38624
39071
|
init_quota_errors();
|
|
38625
39072
|
var groupStatusSchema = exports_external.enum(["pending", "running", "terminal"]);
|
|
38626
39073
|
var taskStatusSchema = exports_external.enum([
|
|
38627
|
-
"pending",
|
|
38628
39074
|
"needs_confirmation",
|
|
38629
39075
|
"running",
|
|
38630
39076
|
"blocked",
|
|
@@ -38641,11 +39087,11 @@ var taskQuestionSchema = exports_external.object({
|
|
|
38641
39087
|
questionId: exports_external.string().min(1)
|
|
38642
39088
|
}).strict();
|
|
38643
39089
|
function buildWeacpxMcpToolRegistry(input) {
|
|
38644
|
-
const { transport, coordinatorSession, sourceHandle, availableAgents } = input;
|
|
38645
|
-
|
|
39090
|
+
const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, availableAgents } = input;
|
|
39091
|
+
const tools = [
|
|
38646
39092
|
{
|
|
38647
39093
|
name: "delegate_request",
|
|
38648
|
-
description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker.${availableAgents && availableAgents.length > 0 ? ` Available agents: ${availableAgents.join(", ")}.` : ""}`,
|
|
39094
|
+
description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker. After this returns status=running, call task_wait with the returned taskId to wait for completion before reporting back to the user; if status=needs_confirmation, wait for the user to approve (task_approve / task_reject) and do not call task_wait yet.${availableAgents && availableAgents.length > 0 ? ` Available agents: ${availableAgents.join(", ")}.` : ""}`,
|
|
38649
39095
|
inputSchema: exports_external.object({
|
|
38650
39096
|
targetAgent: exports_external.string().min(1),
|
|
38651
39097
|
task: exports_external.string().min(1),
|
|
@@ -38665,7 +39111,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38665
39111
|
},
|
|
38666
39112
|
{
|
|
38667
39113
|
name: "group_new",
|
|
38668
|
-
description: "Create a new task group under the current coordinator.",
|
|
39114
|
+
description: "Create a new task group under the current coordinator. Use to batch multiple delegate_request calls together; pass the resulting groupId on each delegate so they share lifecycle and cancellation.",
|
|
38669
39115
|
inputSchema: exports_external.object({
|
|
38670
39116
|
title: exports_external.string().min(1)
|
|
38671
39117
|
}).strict(),
|
|
@@ -38679,7 +39125,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38679
39125
|
},
|
|
38680
39126
|
{
|
|
38681
39127
|
name: "group_get",
|
|
38682
|
-
description: "Fetch a single task-group summary under the current coordinator.",
|
|
39128
|
+
description: "Fetch a single task-group summary under the current coordinator. Use to check aggregate progress when waiting on a batch of delegations.",
|
|
38683
39129
|
inputSchema: exports_external.object({
|
|
38684
39130
|
groupId: exports_external.string().min(1)
|
|
38685
39131
|
}).strict(),
|
|
@@ -38693,7 +39139,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38693
39139
|
},
|
|
38694
39140
|
{
|
|
38695
39141
|
name: "group_list",
|
|
38696
|
-
description: "List task groups under the current coordinator.",
|
|
39142
|
+
description: "List task groups under the current coordinator. Use to recover groupIds for an earlier batch.",
|
|
38697
39143
|
inputSchema: exports_external.object({
|
|
38698
39144
|
status: groupStatusSchema.optional(),
|
|
38699
39145
|
stuck: exports_external.boolean().optional(),
|
|
@@ -38714,7 +39160,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38714
39160
|
},
|
|
38715
39161
|
{
|
|
38716
39162
|
name: "group_cancel",
|
|
38717
|
-
description: "Cancel all unfinished tasks in a task group under the current coordinator.",
|
|
39163
|
+
description: "Cancel all unfinished tasks in a task group under the current coordinator. Use to abort a batch started via group_new + delegate_request.",
|
|
38718
39164
|
inputSchema: exports_external.object({
|
|
38719
39165
|
groupId: exports_external.string().min(1)
|
|
38720
39166
|
}).strict(),
|
|
@@ -38728,7 +39174,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38728
39174
|
},
|
|
38729
39175
|
{
|
|
38730
39176
|
name: "task_get",
|
|
38731
|
-
description: "Fetch a single task under the current coordinator.",
|
|
39177
|
+
description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use after task_wait returns to read the actual output before summarizing it for the user, or to inspect a task that requires attention.",
|
|
38732
39178
|
inputSchema: exports_external.object({
|
|
38733
39179
|
taskId: exports_external.string().min(1)
|
|
38734
39180
|
}).strict(),
|
|
@@ -38742,7 +39188,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38742
39188
|
},
|
|
38743
39189
|
{
|
|
38744
39190
|
name: "task_list",
|
|
38745
|
-
description: "List tasks under the current coordinator.",
|
|
39191
|
+
description: "List tasks under the current coordinator. Use to recover taskIds for in-flight delegations or to survey what is still running / blocked.",
|
|
38746
39192
|
inputSchema: exports_external.object({
|
|
38747
39193
|
status: taskStatusSchema.optional(),
|
|
38748
39194
|
stuck: exports_external.boolean().optional(),
|
|
@@ -38763,7 +39209,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38763
39209
|
},
|
|
38764
39210
|
{
|
|
38765
39211
|
name: "task_approve",
|
|
38766
|
-
description: "Approve a pending task under the current coordinator.",
|
|
39212
|
+
description: "Approve a pending task under the current coordinator. Use when delegate_request returned status=needs_confirmation and the user has authorized it; after approval, call task_wait.",
|
|
38767
39213
|
inputSchema: exports_external.object({
|
|
38768
39214
|
taskId: exports_external.string().min(1)
|
|
38769
39215
|
}).strict(),
|
|
@@ -38777,7 +39223,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38777
39223
|
},
|
|
38778
39224
|
{
|
|
38779
39225
|
name: "task_reject",
|
|
38780
|
-
description: "Reject a pending task under the current coordinator.",
|
|
39226
|
+
description: "Reject a pending task under the current coordinator. Use when delegate_request returned status=needs_confirmation and the user declined; no task_wait is needed afterwards.",
|
|
38781
39227
|
inputSchema: exports_external.object({
|
|
38782
39228
|
taskId: exports_external.string().min(1)
|
|
38783
39229
|
}).strict(),
|
|
@@ -38791,7 +39237,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38791
39237
|
},
|
|
38792
39238
|
{
|
|
38793
39239
|
name: "task_cancel",
|
|
38794
|
-
description: "Request cancellation for a task under the current coordinator.",
|
|
39240
|
+
description: "Request cancellation for a task under the current coordinator. Use to abort a running delegation; the task transitions to a terminal state shortly after.",
|
|
38795
39241
|
inputSchema: exports_external.object({
|
|
38796
39242
|
taskId: exports_external.string().min(1)
|
|
38797
39243
|
}).strict(),
|
|
@@ -38805,7 +39251,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38805
39251
|
},
|
|
38806
39252
|
{
|
|
38807
39253
|
name: "task_wait",
|
|
38808
|
-
description: `Wait for a task to finish or require attention. Defaults: timeout ${DEFAULT_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${DEFAULT_TASK_WAIT_POLL_INTERVAL_MS} ms. Maximums: timeout ${MAX_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${MAX_TASK_WAIT_POLL_INTERVAL_MS} ms.`,
|
|
39254
|
+
description: `Wait for a task to finish or require attention. Call this immediately after delegate_request (when status=running) unless you intend a fire-and-forget. Returns status=terminal (done; call task_get for the result), status=attention_required (call task_get first to read the task's current status, then branch: needs_confirmation -> task_approve or task_reject; blocked or waiting_for_human -> coordinator_answer_question; reviewPending set -> coordinator_review_contested_result; after resolving, call task_wait again), or status=timeout (still running; call task_wait again or task_get for a snapshot). Defaults: timeout ${DEFAULT_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${DEFAULT_TASK_WAIT_POLL_INTERVAL_MS} ms. Maximums: timeout ${MAX_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${MAX_TASK_WAIT_POLL_INTERVAL_MS} ms.`,
|
|
38809
39255
|
inputSchema: exports_external.object({
|
|
38810
39256
|
taskId: exports_external.string().min(1),
|
|
38811
39257
|
timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
|
|
@@ -38821,7 +39267,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38821
39267
|
},
|
|
38822
39268
|
{
|
|
38823
39269
|
name: "worker_raise_question",
|
|
38824
|
-
description: "Raise a blocker question for the current bound worker session.",
|
|
39270
|
+
description: "Raise a blocker question for the current bound worker session. Worker-side only: call this from inside a delegated task when you are blocked and need the coordinator's input. Coordinators waiting on a delegation should not call this; use task_wait instead.",
|
|
38825
39271
|
inputSchema: exports_external.object({
|
|
38826
39272
|
taskId: exports_external.string().min(1),
|
|
38827
39273
|
question: exports_external.string().min(1),
|
|
@@ -38841,7 +39287,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38841
39287
|
},
|
|
38842
39288
|
{
|
|
38843
39289
|
name: "coordinator_answer_question",
|
|
38844
|
-
description: "Answer a blocked worker question under the current coordinator.",
|
|
39290
|
+
description: "Answer a blocked worker question under the current coordinator. Use after task_wait returns status=attention_required and task_get shows a pending question; after answering, call task_wait again to keep waiting for the worker to finish.",
|
|
38845
39291
|
inputSchema: exports_external.object({
|
|
38846
39292
|
taskId: exports_external.string().min(1),
|
|
38847
39293
|
questionId: exports_external.string().min(1),
|
|
@@ -38857,7 +39303,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38857
39303
|
},
|
|
38858
39304
|
{
|
|
38859
39305
|
name: "coordinator_request_human_input",
|
|
38860
|
-
description: "Create or queue a human question package for blocked tasks under the current coordinator.",
|
|
39306
|
+
description: "Create or queue a human question package for blocked tasks under the current coordinator. Use when answering a worker question requires real human input rather than your own judgement.",
|
|
38861
39307
|
inputSchema: exports_external.object({
|
|
38862
39308
|
taskQuestions: exports_external.array(taskQuestionSchema).min(1),
|
|
38863
39309
|
promptText: exports_external.string().min(1),
|
|
@@ -38873,7 +39319,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38873
39319
|
},
|
|
38874
39320
|
{
|
|
38875
39321
|
name: "coordinator_follow_up_human_package",
|
|
38876
|
-
description: "Append a follow-up message to the active human question package under the current coordinator.",
|
|
39322
|
+
description: "Append a follow-up message to the active human question package under the current coordinator. Use to clarify or add context to an in-flight package created via coordinator_request_human_input.",
|
|
38877
39323
|
inputSchema: exports_external.object({
|
|
38878
39324
|
packageId: exports_external.string().min(1),
|
|
38879
39325
|
priorMessageId: exports_external.string().min(1),
|
|
@@ -38890,7 +39336,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38890
39336
|
},
|
|
38891
39337
|
{
|
|
38892
39338
|
name: "coordinator_review_contested_result",
|
|
38893
|
-
description: "Review a contested result under the current coordinator.",
|
|
39339
|
+
description: "Review a contested result under the current coordinator. Use when a worker's result has been challenged and the coordinator must decide accept or discard.",
|
|
38894
39340
|
inputSchema: exports_external.object({
|
|
38895
39341
|
taskId: exports_external.string().min(1),
|
|
38896
39342
|
reviewId: exports_external.string().min(1),
|
|
@@ -38907,6 +39353,14 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38907
39353
|
})
|
|
38908
39354
|
}
|
|
38909
39355
|
];
|
|
39356
|
+
if (isExternalCoordinator) {
|
|
39357
|
+
const externalCoordinatorIncompatibleTools = new Set([
|
|
39358
|
+
"coordinator_request_human_input",
|
|
39359
|
+
"coordinator_follow_up_human_package"
|
|
39360
|
+
]);
|
|
39361
|
+
return tools.filter((tool) => !externalCoordinatorIncompatibleTools.has(tool.name));
|
|
39362
|
+
}
|
|
39363
|
+
return tools;
|
|
38910
39364
|
}
|
|
38911
39365
|
async function asToolResult(action) {
|
|
38912
39366
|
try {
|
|
@@ -38935,12 +39389,28 @@ function renderTaskWaitResult(result) {
|
|
|
38935
39389
|
return `Task wait ${result.status.replace("_", " ")}; current state is unavailable.`;
|
|
38936
39390
|
}
|
|
38937
39391
|
if (result.status === "timeout") {
|
|
38938
|
-
return
|
|
39392
|
+
return [
|
|
39393
|
+
`Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`,
|
|
39394
|
+
`Next: call task_wait again with this taskId to keep waiting, or task_get for a snapshot.`
|
|
39395
|
+
].join(`
|
|
39396
|
+
`);
|
|
38939
39397
|
}
|
|
38940
39398
|
if (result.status === "attention_required") {
|
|
38941
|
-
return
|
|
39399
|
+
return [
|
|
39400
|
+
`Task ${result.task.taskId} requires attention; current state is ${result.task.status}.`,
|
|
39401
|
+
`Next: call task_get to read the task's current status and any reviewPending / openQuestion fields, then branch by what you see:`,
|
|
39402
|
+
` - status=needs_confirmation -> task_approve or task_reject`,
|
|
39403
|
+
` - status=blocked or waiting_for_human -> coordinator_answer_question`,
|
|
39404
|
+
` - reviewPending set -> coordinator_review_contested_result`,
|
|
39405
|
+
`After resolving, call task_wait again to keep waiting for the worker to finish.`
|
|
39406
|
+
].join(`
|
|
39407
|
+
`);
|
|
38942
39408
|
}
|
|
38943
|
-
return
|
|
39409
|
+
return [
|
|
39410
|
+
`Task ${result.task.taskId} reached terminal state ${result.task.status}.`,
|
|
39411
|
+
`Next: call task_get to read the worker's final result before reporting back to the user.`
|
|
39412
|
+
].join(`
|
|
39413
|
+
`);
|
|
38944
39414
|
}
|
|
38945
39415
|
function createSuccessResult(text, structuredContent) {
|
|
38946
39416
|
return {
|
|
@@ -38955,7 +39425,8 @@ function createErrorResult(message) {
|
|
|
38955
39425
|
};
|
|
38956
39426
|
}
|
|
38957
39427
|
function renderDelegateSuccess(result) {
|
|
38958
|
-
|
|
39428
|
+
const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval; do not call task_wait yet. Tell the user, then call task_approve or task_reject based on their response.` : `Next: call task_wait with taskId="${result.taskId}" to wait for the worker to finish, then task_get to read the result before reporting back.`;
|
|
39429
|
+
return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
|
|
38959
39430
|
`);
|
|
38960
39431
|
}
|
|
38961
39432
|
function renderGroupCreated(group) {
|
|
@@ -39100,7 +39571,11 @@ function renderTaskCancelRequest(task) {
|
|
|
39100
39571
|
`);
|
|
39101
39572
|
}
|
|
39102
39573
|
function renderTaskApprovalSuccess(task) {
|
|
39103
|
-
return [
|
|
39574
|
+
return [
|
|
39575
|
+
`Task "${task.taskId}" approved.`,
|
|
39576
|
+
`- Current status: ${task.status}`,
|
|
39577
|
+
`Next: call task_wait with taskId="${task.taskId}" to wait for the worker to finish, then task_get to read the result before reporting back.`
|
|
39578
|
+
].join(`
|
|
39104
39579
|
`);
|
|
39105
39580
|
}
|
|
39106
39581
|
function renderTaskRejectionSuccess(task) {
|
|
@@ -39335,6 +39810,30 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
39335
39810
|
}
|
|
39336
39811
|
|
|
39337
39812
|
// src/mcp/weacpx-mcp-server.ts
|
|
39813
|
+
var WEACPX_MCP_SERVER_INSTRUCTIONS = [
|
|
39814
|
+
"Use these tools to orchestrate work across other agents under your coordinator session.",
|
|
39815
|
+
"",
|
|
39816
|
+
"Typical lifecycle for a single delegation:",
|
|
39817
|
+
"1. delegate_request → returns { taskId, status }.",
|
|
39818
|
+
" - status=running: the worker has started; go to step 2.",
|
|
39819
|
+
" - status=needs_confirmation: tell the user, then call task_approve or task_reject based on their response. After task_approve, return to step 2 to wait for the worker. Do not call task_wait before approval.",
|
|
39820
|
+
"2. task_wait(taskId) → blocks until the task is done, needs attention, or times out.",
|
|
39821
|
+
" - status=terminal: go to step 3.",
|
|
39822
|
+
" - status=attention_required: the task is in needs_confirmation / blocked / waiting_for_human, or has reviewPending set. Call task_get(taskId) to read the actual status and any openQuestion / reviewPending fields, then branch:",
|
|
39823
|
+
" * needs_confirmation -> task_approve or task_reject (after approval, go back to step 2)",
|
|
39824
|
+
" * blocked or waiting_for_human -> coordinator_answer_question (the answer can come from you or be relayed from a human you consulted)",
|
|
39825
|
+
" * reviewPending set -> coordinator_review_contested_result with accept or discard",
|
|
39826
|
+
" After resolving, call task_wait again to keep waiting.",
|
|
39827
|
+
" - status=timeout: the task is still running. Call task_wait again to keep waiting, or task_get for a snapshot.",
|
|
39828
|
+
"3. The task is terminal. Call task_get(taskId) to read the worker's final result, then summarize it for the user. Do not invent results that did not come from task_get.",
|
|
39829
|
+
"",
|
|
39830
|
+
"Batching: use group_new before a wave of delegate_request calls and pass groupId on each, then group_get / group_list / group_cancel to manage the batch.",
|
|
39831
|
+
"Cancellation: task_cancel aborts a single running task; group_cancel aborts the whole batch.",
|
|
39832
|
+
"Discovery: task_list / group_list recover taskIds and groupIds from earlier in the session.",
|
|
39833
|
+
"",
|
|
39834
|
+
"worker_raise_question is worker-side only — call it from inside a delegated task when you are blocked, not from the coordinator that is waiting on a delegation."
|
|
39835
|
+
].join(`
|
|
39836
|
+
`);
|
|
39338
39837
|
function createWeacpxMcpServer(options) {
|
|
39339
39838
|
const server = new Server({
|
|
39340
39839
|
name: "weacpx-orchestration",
|
|
@@ -39342,7 +39841,8 @@ function createWeacpxMcpServer(options) {
|
|
|
39342
39841
|
}, {
|
|
39343
39842
|
capabilities: {
|
|
39344
39843
|
tools: {}
|
|
39345
|
-
}
|
|
39844
|
+
},
|
|
39845
|
+
instructions: WEACPX_MCP_SERVER_INSTRUCTIONS
|
|
39346
39846
|
});
|
|
39347
39847
|
let toolState = null;
|
|
39348
39848
|
let toolStatePromise = null;
|
|
@@ -39361,6 +39861,7 @@ function createWeacpxMcpServer(options) {
|
|
|
39361
39861
|
transport: options.transport,
|
|
39362
39862
|
coordinatorSession: identity.coordinatorSession,
|
|
39363
39863
|
...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {},
|
|
39864
|
+
...identity.isExternalCoordinator ? { isExternalCoordinator: true } : {},
|
|
39364
39865
|
...options.availableAgents ? { availableAgents: options.availableAgents } : {}
|
|
39365
39866
|
});
|
|
39366
39867
|
return toolState;
|
|
@@ -39410,7 +39911,8 @@ async function resolveMcpIdentity(server, options) {
|
|
|
39410
39911
|
if (options.coordinatorSession) {
|
|
39411
39912
|
return {
|
|
39412
39913
|
coordinatorSession: options.coordinatorSession,
|
|
39413
|
-
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {}
|
|
39914
|
+
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
|
|
39915
|
+
...options.isExternalCoordinator ? { isExternalCoordinator: true } : {}
|
|
39414
39916
|
};
|
|
39415
39917
|
}
|
|
39416
39918
|
throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
|
|
@@ -39675,7 +40177,7 @@ function sanitizeName(input, fallback) {
|
|
|
39675
40177
|
init_plugin_home();
|
|
39676
40178
|
import { spawn as spawn4 } from "node:child_process";
|
|
39677
40179
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
39678
|
-
import { dirname as
|
|
40180
|
+
import { dirname as dirname9, join as join7 } from "node:path";
|
|
39679
40181
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
39680
40182
|
|
|
39681
40183
|
// src/plugins/package-manager.ts
|
|
@@ -39953,8 +40455,8 @@ async function runInherit(command, args) {
|
|
|
39953
40455
|
}
|
|
39954
40456
|
async function readPackageName() {
|
|
39955
40457
|
try {
|
|
39956
|
-
const here =
|
|
39957
|
-
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")]) {
|
|
39958
40460
|
try {
|
|
39959
40461
|
const parsed = JSON.parse(await readFile7(candidate, "utf8"));
|
|
39960
40462
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
@@ -40580,7 +41082,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
40580
41082
|
// src/plugins/plugin-cli.ts
|
|
40581
41083
|
init_plugin_home();
|
|
40582
41084
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
40583
|
-
import { isAbsolute, join as
|
|
41085
|
+
import { isAbsolute, join as join9, resolve } from "node:path";
|
|
40584
41086
|
init_plugin_loader();
|
|
40585
41087
|
init_validate_plugin();
|
|
40586
41088
|
|
|
@@ -40590,13 +41092,13 @@ init_plugin_loader();
|
|
|
40590
41092
|
init_validate_plugin();
|
|
40591
41093
|
init_known_plugins();
|
|
40592
41094
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
40593
|
-
import { join as
|
|
41095
|
+
import { join as join8 } from "node:path";
|
|
40594
41096
|
function suggestedPluginPackageForChannel(type) {
|
|
40595
41097
|
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
40596
41098
|
}
|
|
40597
41099
|
async function readDependencyEntries(pluginHome) {
|
|
40598
41100
|
try {
|
|
40599
|
-
const raw = await readFile8(
|
|
41101
|
+
const raw = await readFile8(join8(pluginHome, "package.json"), "utf8");
|
|
40600
41102
|
const parsed = JSON.parse(raw);
|
|
40601
41103
|
const out = {};
|
|
40602
41104
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40698,7 +41200,7 @@ function looksLikePath(spec) {
|
|
|
40698
41200
|
}
|
|
40699
41201
|
async function readDependencyEntries2(pluginHome) {
|
|
40700
41202
|
try {
|
|
40701
|
-
const raw = await readFile9(
|
|
41203
|
+
const raw = await readFile9(join9(pluginHome, "package.json"), "utf8");
|
|
40702
41204
|
const parsed = JSON.parse(raw);
|
|
40703
41205
|
const out = {};
|
|
40704
41206
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40724,7 +41226,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
40724
41226
|
return name;
|
|
40725
41227
|
}
|
|
40726
41228
|
try {
|
|
40727
|
-
const raw = await readFile9(
|
|
41229
|
+
const raw = await readFile9(join9(installSpec, "package.json"), "utf8");
|
|
40728
41230
|
const parsed = JSON.parse(raw);
|
|
40729
41231
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
40730
41232
|
return parsed.name.trim();
|
|
@@ -41279,7 +41781,7 @@ function createMcpStdioIdentityResolver(input) {
|
|
|
41279
41781
|
clientName: context.clientName,
|
|
41280
41782
|
...resolvedWorkspace ? { workspace: resolvedWorkspace } : { instanceId }
|
|
41281
41783
|
});
|
|
41282
|
-
await prepareMcpCoordinatorStartup({
|
|
41784
|
+
const startup = await prepareMcpCoordinatorStartup({
|
|
41283
41785
|
coordinatorSession: resolvedCoordinatorSession,
|
|
41284
41786
|
...resolvedWorkspace ? { workspace: resolvedWorkspace } : {},
|
|
41285
41787
|
config: input.config,
|
|
@@ -41288,7 +41790,8 @@ function createMcpStdioIdentityResolver(input) {
|
|
|
41288
41790
|
});
|
|
41289
41791
|
return {
|
|
41290
41792
|
coordinatorSession: resolvedCoordinatorSession,
|
|
41291
|
-
...sourceHandle ? { sourceHandle } : {}
|
|
41793
|
+
...sourceHandle ? { sourceHandle } : {},
|
|
41794
|
+
...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {}
|
|
41292
41795
|
};
|
|
41293
41796
|
};
|
|
41294
41797
|
}
|
|
@@ -41672,6 +42175,7 @@ async function defaultLoadConfiguredPluginsForChannelCli() {
|
|
|
41672
42175
|
const { loadConfiguredPlugins: loadConfiguredPlugins2 } = await Promise.resolve().then(() => (init_plugin_loader(), exports_plugin_loader));
|
|
41673
42176
|
await loadConfiguredPlugins2({ plugins: config2.plugins });
|
|
41674
42177
|
}
|
|
42178
|
+
var DAEMON_RUN_ENV = "WEACPX_DAEMON_RUN";
|
|
41675
42179
|
async function defaultRun(options = {}) {
|
|
41676
42180
|
const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2, prepareChannelMedia: prepareChannelMedia2 }, { runConsole: runConsole2 }] = await Promise.all([
|
|
41677
42181
|
init_main().then(() => exports_main),
|
|
@@ -41700,6 +42204,7 @@ async function defaultRun(options = {}) {
|
|
|
41700
42204
|
await createFirstRunSession(runtime, firstRunOnboarding);
|
|
41701
42205
|
} : undefined,
|
|
41702
42206
|
channels: channelRegistry,
|
|
42207
|
+
channelStartupPolicy: process.env[DAEMON_RUN_ENV] === "1" ? "best-effort" : "require-one",
|
|
41703
42208
|
daemonRuntime,
|
|
41704
42209
|
...firstLockCreator ? {
|
|
41705
42210
|
consumerLockFactory: (runtime) => firstLockCreator.create({
|
|
@@ -41994,7 +42499,7 @@ function safeDaemonLogPaths() {
|
|
|
41994
42499
|
const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
41995
42500
|
const paths = resolveDaemonPaths({ home: requireHome2() });
|
|
41996
42501
|
return {
|
|
41997
|
-
appLog: join16(
|
|
42502
|
+
appLog: join16(dirname15(configPath), "runtime", "app.log"),
|
|
41998
42503
|
stderrLog: paths.stderrLog
|
|
41999
42504
|
};
|
|
42000
42505
|
} catch {
|