weacpx 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -5
- package/config.example.json +7 -1
- package/dist/bridge/bridge-main.js +11 -25
- package/dist/channels/types.d.ts +2 -0
- package/dist/cli.js +1769 -457
- 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 +12 -0
- package/dist/perf/perf-log-writer.d.ts +25 -0
- package/dist/perf/perf-tracer.d.ts +54 -0
- package/dist/weixin/agent/interface.d.ts +7 -0
- package/dist/weixin/bot.d.ts +2 -0
- package/dist/weixin/messaging/handle-weixin-message-turn.d.ts +2 -0
- package/dist/weixin/monitor/monitor.d.ts +2 -0
- package/package.json +2 -2
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",
|
|
@@ -2567,6 +2611,82 @@ var init_ensure_config = __esm(() => {
|
|
|
2567
2611
|
};
|
|
2568
2612
|
});
|
|
2569
2613
|
|
|
2614
|
+
// src/config/agent-templates.ts
|
|
2615
|
+
function getAgentTemplate(name) {
|
|
2616
|
+
const template = TEMPLATES[name];
|
|
2617
|
+
if (!template) {
|
|
2618
|
+
return null;
|
|
2619
|
+
}
|
|
2620
|
+
return {
|
|
2621
|
+
...template
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function listAgentTemplates() {
|
|
2625
|
+
return Object.keys(TEMPLATES);
|
|
2626
|
+
}
|
|
2627
|
+
function sameAgentConfig(left, right) {
|
|
2628
|
+
return left.driver === right.driver && (left.command ?? "") === (right.command ?? "");
|
|
2629
|
+
}
|
|
2630
|
+
var TEMPLATES;
|
|
2631
|
+
var init_agent_templates = __esm(() => {
|
|
2632
|
+
TEMPLATES = {
|
|
2633
|
+
codex: {
|
|
2634
|
+
driver: "codex"
|
|
2635
|
+
},
|
|
2636
|
+
claude: {
|
|
2637
|
+
driver: "claude"
|
|
2638
|
+
},
|
|
2639
|
+
pi: {
|
|
2640
|
+
driver: "pi"
|
|
2641
|
+
},
|
|
2642
|
+
openclaw: {
|
|
2643
|
+
driver: "openclaw"
|
|
2644
|
+
},
|
|
2645
|
+
gemini: {
|
|
2646
|
+
driver: "gemini"
|
|
2647
|
+
},
|
|
2648
|
+
cursor: {
|
|
2649
|
+
driver: "cursor"
|
|
2650
|
+
},
|
|
2651
|
+
copilot: {
|
|
2652
|
+
driver: "copilot"
|
|
2653
|
+
},
|
|
2654
|
+
droid: {
|
|
2655
|
+
driver: "droid"
|
|
2656
|
+
},
|
|
2657
|
+
"factory-droid": {
|
|
2658
|
+
driver: "factory-droid"
|
|
2659
|
+
},
|
|
2660
|
+
factorydroid: {
|
|
2661
|
+
driver: "factorydroid"
|
|
2662
|
+
},
|
|
2663
|
+
iflow: {
|
|
2664
|
+
driver: "iflow"
|
|
2665
|
+
},
|
|
2666
|
+
kilocode: {
|
|
2667
|
+
driver: "kilocode"
|
|
2668
|
+
},
|
|
2669
|
+
kimi: {
|
|
2670
|
+
driver: "kimi"
|
|
2671
|
+
},
|
|
2672
|
+
kiro: {
|
|
2673
|
+
driver: "kiro"
|
|
2674
|
+
},
|
|
2675
|
+
opencode: {
|
|
2676
|
+
driver: "opencode"
|
|
2677
|
+
},
|
|
2678
|
+
qoder: {
|
|
2679
|
+
driver: "qoder"
|
|
2680
|
+
},
|
|
2681
|
+
qwen: {
|
|
2682
|
+
driver: "qwen"
|
|
2683
|
+
},
|
|
2684
|
+
trae: {
|
|
2685
|
+
driver: "trae"
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2570
2690
|
// src/daemon/daemon-status.ts
|
|
2571
2691
|
import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
2572
2692
|
import { dirname as dirname2 } from "node:path";
|
|
@@ -2837,6 +2957,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2837
2957
|
WEACPX_DAEMON_ARG0: options.cliEntryPath,
|
|
2838
2958
|
WEACPX_DAEMON_ARG1: "run",
|
|
2839
2959
|
WEACPX_DAEMON_CWD: options.cwd,
|
|
2960
|
+
WEACPX_DAEMON_RUN: "1",
|
|
2840
2961
|
WEACPX_DAEMON_STDOUT: paths.stdoutLog,
|
|
2841
2962
|
WEACPX_DAEMON_STDERR: paths.stderrLog,
|
|
2842
2963
|
...spawnOptions.firstRunOnboarding ? { WEACPX_FIRST_RUN_ONBOARDING: spawnOptions.firstRunOnboarding } : {}
|
|
@@ -2855,6 +2976,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2855
2976
|
detached: true,
|
|
2856
2977
|
env: {
|
|
2857
2978
|
...options.env,
|
|
2979
|
+
WEACPX_DAEMON_RUN: "1",
|
|
2858
2980
|
...spawnOptions.firstRunOnboarding ? { WEACPX_FIRST_RUN_ONBOARDING: spawnOptions.firstRunOnboarding } : {}
|
|
2859
2981
|
},
|
|
2860
2982
|
stdio: ["ignore", stdoutFd, stderrFd]
|
|
@@ -2863,6 +2985,7 @@ function buildSpawnRequest(paths, options, stdoutFd, stderrFd, spawnOptions = {}
|
|
|
2863
2985
|
}
|
|
2864
2986
|
function buildWindowsLauncherScript() {
|
|
2865
2987
|
const script = [
|
|
2988
|
+
"$env:WEACPX_DAEMON_RUN = '1'",
|
|
2866
2989
|
"$process = Start-Process -FilePath $env:WEACPX_DAEMON_COMMAND `",
|
|
2867
2990
|
" -ArgumentList @($env:WEACPX_DAEMON_ARG0, $env:WEACPX_DAEMON_ARG1) `",
|
|
2868
2991
|
" -WorkingDirectory $env:WEACPX_DAEMON_CWD `",
|
|
@@ -9495,10 +9618,11 @@ var PACKAGE_NAME = "weacpx";
|
|
|
9495
9618
|
var init_version = () => {};
|
|
9496
9619
|
|
|
9497
9620
|
// src/orchestration/task-wait-timeouts.ts
|
|
9498
|
-
var DEFAULT_TASK_WAIT_TIMEOUT_MS, MAX_TASK_WAIT_TIMEOUT_MS, DEFAULT_TASK_WAIT_POLL_INTERVAL_MS = 1000, MAX_TASK_WAIT_POLL_INTERVAL_MS = 1e4, TASK_WAIT_RPC_TIMEOUT_PADDING_MS = 5000;
|
|
9621
|
+
var DEFAULT_TASK_WAIT_TIMEOUT_MS, MAX_TASK_WAIT_TIMEOUT_MS, DEFAULT_TASK_WAIT_POLL_INTERVAL_MS = 1000, MAX_TASK_WAIT_POLL_INTERVAL_MS = 1e4, TASK_WAIT_RPC_TIMEOUT_PADDING_MS = 5000, DEFAULT_TASK_WATCH_TIMEOUT_MS = 60000, MAX_TASK_WATCH_TIMEOUT_MS, DEFAULT_TASK_WATCH_POLL_INTERVAL_MS = 1000, MAX_TASK_WATCH_POLL_INTERVAL_MS = 1e4, TASK_WATCH_RPC_TIMEOUT_PADDING_MS = 5000;
|
|
9499
9622
|
var init_task_wait_timeouts = __esm(() => {
|
|
9500
9623
|
DEFAULT_TASK_WAIT_TIMEOUT_MS = 5 * 60000;
|
|
9501
9624
|
MAX_TASK_WAIT_TIMEOUT_MS = 20 * 60000;
|
|
9625
|
+
MAX_TASK_WATCH_TIMEOUT_MS = 20 * 60000;
|
|
9502
9626
|
});
|
|
9503
9627
|
|
|
9504
9628
|
// src/weixin/messaging/quota-errors.ts
|
|
@@ -9560,6 +9684,15 @@ function isTaskStatus(value) {
|
|
|
9560
9684
|
function isSourceKind(value) {
|
|
9561
9685
|
return value === "human" || value === "coordinator" || value === "worker";
|
|
9562
9686
|
}
|
|
9687
|
+
function isOptionalNumber(value) {
|
|
9688
|
+
return value === undefined || typeof value === "number";
|
|
9689
|
+
}
|
|
9690
|
+
function isTaskEventRecord(value) {
|
|
9691
|
+
if (!isRecord2(value)) {
|
|
9692
|
+
return false;
|
|
9693
|
+
}
|
|
9694
|
+
return typeof value.seq === "number" && isString(value.at) && (value.type === "created" || value.type === "progress" || value.type === "status_changed" || value.type === "attention_required" || value.type === "cancel_requested") && (value.status === undefined || isTaskStatus(value.status)) && isOptionalString(value.summary) && isOptionalString(value.message);
|
|
9695
|
+
}
|
|
9563
9696
|
function isOpenQuestionRecord(value) {
|
|
9564
9697
|
if (!isRecord2(value)) {
|
|
9565
9698
|
return false;
|
|
@@ -9582,7 +9715,7 @@ function isTaskRecord(value) {
|
|
|
9582
9715
|
if (!isRecord2(value)) {
|
|
9583
9716
|
return false;
|
|
9584
9717
|
}
|
|
9585
|
-
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending));
|
|
9718
|
+
return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.lastProgressSummary) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending)) && isOptionalNumber(value.eventSeq) && (value.events === undefined || Array.isArray(value.events) && value.events.every(isTaskEventRecord));
|
|
9586
9719
|
}
|
|
9587
9720
|
function isExternalCoordinatorRecord(value) {
|
|
9588
9721
|
if (!isRecord2(value)) {
|
|
@@ -9846,37 +9979,6 @@ var init_state_store = __esm(() => {
|
|
|
9846
9979
|
init_types();
|
|
9847
9980
|
});
|
|
9848
9981
|
|
|
9849
|
-
// src/config/agent-templates.ts
|
|
9850
|
-
function getAgentTemplate(name) {
|
|
9851
|
-
const template = TEMPLATES[name];
|
|
9852
|
-
if (!template) {
|
|
9853
|
-
return null;
|
|
9854
|
-
}
|
|
9855
|
-
return {
|
|
9856
|
-
...template
|
|
9857
|
-
};
|
|
9858
|
-
}
|
|
9859
|
-
function listAgentTemplates() {
|
|
9860
|
-
return Object.keys(TEMPLATES);
|
|
9861
|
-
}
|
|
9862
|
-
var TEMPLATES;
|
|
9863
|
-
var init_agent_templates = __esm(() => {
|
|
9864
|
-
TEMPLATES = {
|
|
9865
|
-
codex: {
|
|
9866
|
-
driver: "codex"
|
|
9867
|
-
},
|
|
9868
|
-
claude: {
|
|
9869
|
-
driver: "claude"
|
|
9870
|
-
},
|
|
9871
|
-
opencode: {
|
|
9872
|
-
driver: "opencode"
|
|
9873
|
-
},
|
|
9874
|
-
gemini: {
|
|
9875
|
-
driver: "gemini"
|
|
9876
|
-
}
|
|
9877
|
-
};
|
|
9878
|
-
});
|
|
9879
|
-
|
|
9880
9982
|
// src/plugins/plugin-home.ts
|
|
9881
9983
|
import { mkdir as mkdir6, writeFile as writeFile5 } from "node:fs/promises";
|
|
9882
9984
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -13147,6 +13249,296 @@ function normalizeMediaArray(media) {
|
|
|
13147
13249
|
return Array.isArray(media) ? media : [media];
|
|
13148
13250
|
}
|
|
13149
13251
|
|
|
13252
|
+
// src/logging/rotating-file-writer.ts
|
|
13253
|
+
import { readdir as readdir2, rename, rm as rm5, stat as stat2 } from "node:fs/promises";
|
|
13254
|
+
import { basename, dirname as dirname6, join as join4 } from "node:path";
|
|
13255
|
+
async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
|
|
13256
|
+
let currentSize = 0;
|
|
13257
|
+
try {
|
|
13258
|
+
currentSize = (await stat2(filePath)).size;
|
|
13259
|
+
} catch (error2) {
|
|
13260
|
+
if (!isMissingFileError2(error2)) {
|
|
13261
|
+
throw error2;
|
|
13262
|
+
}
|
|
13263
|
+
}
|
|
13264
|
+
if (currentSize + incomingSize <= maxSizeBytes) {
|
|
13265
|
+
return;
|
|
13266
|
+
}
|
|
13267
|
+
if (currentSize === 0) {
|
|
13268
|
+
return;
|
|
13269
|
+
}
|
|
13270
|
+
if (maxFiles <= 0) {
|
|
13271
|
+
await rm5(filePath, { force: true });
|
|
13272
|
+
return;
|
|
13273
|
+
}
|
|
13274
|
+
await rm5(`${filePath}.${maxFiles}`, { force: true });
|
|
13275
|
+
for (let index = maxFiles - 1;index >= 1; index -= 1) {
|
|
13276
|
+
const source = `${filePath}.${index}`;
|
|
13277
|
+
try {
|
|
13278
|
+
await rename(source, `${filePath}.${index + 1}`);
|
|
13279
|
+
} catch (error2) {
|
|
13280
|
+
if (!isMissingFileError2(error2)) {
|
|
13281
|
+
throw error2;
|
|
13282
|
+
}
|
|
13283
|
+
}
|
|
13284
|
+
}
|
|
13285
|
+
await rename(filePath, `${filePath}.1`);
|
|
13286
|
+
}
|
|
13287
|
+
async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
|
|
13288
|
+
const parentDir = dirname6(filePath);
|
|
13289
|
+
const prefix = `${basename(filePath)}.`;
|
|
13290
|
+
const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
|
|
13291
|
+
let files = [];
|
|
13292
|
+
try {
|
|
13293
|
+
files = await readdir2(parentDir);
|
|
13294
|
+
} catch (error2) {
|
|
13295
|
+
if (isMissingFileError2(error2)) {
|
|
13296
|
+
return;
|
|
13297
|
+
}
|
|
13298
|
+
throw error2;
|
|
13299
|
+
}
|
|
13300
|
+
for (const file of files) {
|
|
13301
|
+
if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
|
|
13302
|
+
continue;
|
|
13303
|
+
}
|
|
13304
|
+
const candidate = join4(parentDir, file);
|
|
13305
|
+
const details = await stat2(candidate);
|
|
13306
|
+
if (details.mtime.getTime() < cutoff) {
|
|
13307
|
+
await rm5(candidate, { force: true });
|
|
13308
|
+
}
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
function isMissingFileError2(error2) {
|
|
13312
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
13313
|
+
}
|
|
13314
|
+
var init_rotating_file_writer = () => {};
|
|
13315
|
+
|
|
13316
|
+
// src/perf/perf-log-writer.ts
|
|
13317
|
+
import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
|
|
13318
|
+
import { dirname as dirname7 } from "node:path";
|
|
13319
|
+
function createPerfLogWriter(options) {
|
|
13320
|
+
const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
|
|
13321
|
+
const mkdir8 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
|
|
13322
|
+
return;
|
|
13323
|
+
}));
|
|
13324
|
+
const now = options.now ?? (() => new Date);
|
|
13325
|
+
const threshold = options.failureThreshold ?? 5;
|
|
13326
|
+
let pending = [];
|
|
13327
|
+
let writeChain = Promise.resolve();
|
|
13328
|
+
let consecutiveFailures = 0;
|
|
13329
|
+
let disabled = false;
|
|
13330
|
+
let notified = false;
|
|
13331
|
+
const writer = {
|
|
13332
|
+
enqueue(line) {
|
|
13333
|
+
if (disabled)
|
|
13334
|
+
return;
|
|
13335
|
+
pending.push(line);
|
|
13336
|
+
scheduleDrain();
|
|
13337
|
+
},
|
|
13338
|
+
async flush() {
|
|
13339
|
+
await scheduleDrain();
|
|
13340
|
+
await writeChain;
|
|
13341
|
+
},
|
|
13342
|
+
async cleanup() {
|
|
13343
|
+
if (disabled)
|
|
13344
|
+
return;
|
|
13345
|
+
try {
|
|
13346
|
+
await cleanupExpiredRotatedLogs(options.filePath, options.retentionDays ?? 7, now);
|
|
13347
|
+
} catch {}
|
|
13348
|
+
},
|
|
13349
|
+
isDisabled() {
|
|
13350
|
+
return disabled;
|
|
13351
|
+
}
|
|
13352
|
+
};
|
|
13353
|
+
return writer;
|
|
13354
|
+
function scheduleDrain() {
|
|
13355
|
+
if (disabled || pending.length === 0) {
|
|
13356
|
+
return writeChain;
|
|
13357
|
+
}
|
|
13358
|
+
const batch = pending;
|
|
13359
|
+
pending = [];
|
|
13360
|
+
writeChain = writeChain.catch(() => {}).then(() => drainBatch(batch));
|
|
13361
|
+
return writeChain;
|
|
13362
|
+
}
|
|
13363
|
+
async function drainBatch(batch) {
|
|
13364
|
+
if (disabled)
|
|
13365
|
+
return;
|
|
13366
|
+
const data = batch.join("");
|
|
13367
|
+
try {
|
|
13368
|
+
await mkdir8(dirname7(options.filePath), { recursive: true });
|
|
13369
|
+
await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
|
|
13370
|
+
await append(options.filePath, data);
|
|
13371
|
+
consecutiveFailures = 0;
|
|
13372
|
+
} catch (err) {
|
|
13373
|
+
consecutiveFailures += 1;
|
|
13374
|
+
if (consecutiveFailures >= threshold) {
|
|
13375
|
+
disabled = true;
|
|
13376
|
+
pending = [];
|
|
13377
|
+
if (!notified) {
|
|
13378
|
+
notified = true;
|
|
13379
|
+
options.onPermanentFailure({
|
|
13380
|
+
perfLogPath: options.filePath,
|
|
13381
|
+
failureCount: consecutiveFailures,
|
|
13382
|
+
lastError: err instanceof Error ? err.message : String(err)
|
|
13383
|
+
});
|
|
13384
|
+
}
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
var init_perf_log_writer = __esm(() => {
|
|
13390
|
+
init_rotating_file_writer();
|
|
13391
|
+
});
|
|
13392
|
+
|
|
13393
|
+
// src/perf/perf-tracer.ts
|
|
13394
|
+
import { randomBytes } from "node:crypto";
|
|
13395
|
+
function createNoopPerfTracer() {
|
|
13396
|
+
return {
|
|
13397
|
+
async wrapTurn(_seed, run) {
|
|
13398
|
+
return run(NOOP_SPAN);
|
|
13399
|
+
},
|
|
13400
|
+
async flush() {},
|
|
13401
|
+
async cleanup() {}
|
|
13402
|
+
};
|
|
13403
|
+
}
|
|
13404
|
+
function createPerfTracer(options) {
|
|
13405
|
+
const now = options.now ?? (() => performance.now());
|
|
13406
|
+
const isoNow = options.isoNow ?? (() => new Date);
|
|
13407
|
+
const randomId = options.randomId ?? defaultRandomId;
|
|
13408
|
+
const formatLine = options.formatLine ?? defaultFormatLine;
|
|
13409
|
+
const formatSummary = options.formatSummaryLine ?? defaultFormatSummaryLine;
|
|
13410
|
+
let disabled = false;
|
|
13411
|
+
const writer = createPerfLogWriter({
|
|
13412
|
+
filePath: options.filePath,
|
|
13413
|
+
maxSizeBytes: options.maxSizeBytes,
|
|
13414
|
+
maxFiles: options.maxFiles,
|
|
13415
|
+
retentionDays: options.retentionDays,
|
|
13416
|
+
onPermanentFailure: (info) => {
|
|
13417
|
+
disabled = true;
|
|
13418
|
+
options.appLogger.error("perf.disabled_due_to_io_error", "perf logging disabled after repeated IO failures", {
|
|
13419
|
+
perfLogPath: info.perfLogPath,
|
|
13420
|
+
failureCount: info.failureCount,
|
|
13421
|
+
lastError: info.lastError
|
|
13422
|
+
}).catch(() => {});
|
|
13423
|
+
}
|
|
13424
|
+
});
|
|
13425
|
+
return {
|
|
13426
|
+
async wrapTurn(seed, run) {
|
|
13427
|
+
if (disabled) {
|
|
13428
|
+
return run(NOOP_SPAN);
|
|
13429
|
+
}
|
|
13430
|
+
const traceId = randomId();
|
|
13431
|
+
let startTime;
|
|
13432
|
+
const marks = [];
|
|
13433
|
+
let lastMarkTime;
|
|
13434
|
+
let explicitOutcome;
|
|
13435
|
+
let outcomeContext;
|
|
13436
|
+
const span = {
|
|
13437
|
+
traceId,
|
|
13438
|
+
mark(event, context) {
|
|
13439
|
+
if (disabled)
|
|
13440
|
+
return;
|
|
13441
|
+
try {
|
|
13442
|
+
const t = now();
|
|
13443
|
+
if (startTime === undefined) {
|
|
13444
|
+
startTime = t;
|
|
13445
|
+
lastMarkTime = t;
|
|
13446
|
+
}
|
|
13447
|
+
const since = t - startTime;
|
|
13448
|
+
const sinceLast = t - lastMarkTime;
|
|
13449
|
+
lastMarkTime = t;
|
|
13450
|
+
marks.push({ e: event, t: Math.round(since) });
|
|
13451
|
+
const line = formatLine({
|
|
13452
|
+
isoNow: isoNow(),
|
|
13453
|
+
event,
|
|
13454
|
+
traceId,
|
|
13455
|
+
chatKey: seed.chatKey,
|
|
13456
|
+
sinceStartMs: Math.round(since),
|
|
13457
|
+
sinceLastMs: Math.round(sinceLast),
|
|
13458
|
+
context
|
|
13459
|
+
});
|
|
13460
|
+
writer.enqueue(line);
|
|
13461
|
+
} catch {}
|
|
13462
|
+
},
|
|
13463
|
+
setOutcome(outcome, context) {
|
|
13464
|
+
explicitOutcome = outcome;
|
|
13465
|
+
outcomeContext = context;
|
|
13466
|
+
}
|
|
13467
|
+
};
|
|
13468
|
+
let thrown;
|
|
13469
|
+
try {
|
|
13470
|
+
return await run(span);
|
|
13471
|
+
} catch (err) {
|
|
13472
|
+
thrown = err;
|
|
13473
|
+
throw err;
|
|
13474
|
+
} finally {
|
|
13475
|
+
try {
|
|
13476
|
+
if (!disabled) {
|
|
13477
|
+
let outcome;
|
|
13478
|
+
if (explicitOutcome !== undefined) {
|
|
13479
|
+
outcome = explicitOutcome;
|
|
13480
|
+
} else if (thrown !== undefined) {
|
|
13481
|
+
outcome = "error";
|
|
13482
|
+
} else {
|
|
13483
|
+
outcome = "ok";
|
|
13484
|
+
}
|
|
13485
|
+
const t = now();
|
|
13486
|
+
const effectiveStart = startTime ?? t;
|
|
13487
|
+
const summary = formatSummary({
|
|
13488
|
+
isoNow: isoNow(),
|
|
13489
|
+
traceId,
|
|
13490
|
+
chatKey: seed.chatKey,
|
|
13491
|
+
kind: seed.kind,
|
|
13492
|
+
outcome,
|
|
13493
|
+
totalMs: Math.round(t - effectiveStart),
|
|
13494
|
+
marks,
|
|
13495
|
+
outcomeContext
|
|
13496
|
+
});
|
|
13497
|
+
writer.enqueue(summary);
|
|
13498
|
+
}
|
|
13499
|
+
} catch {}
|
|
13500
|
+
}
|
|
13501
|
+
},
|
|
13502
|
+
async flush() {
|
|
13503
|
+
await writer.flush();
|
|
13504
|
+
},
|
|
13505
|
+
async cleanup() {
|
|
13506
|
+
await writer.cleanup();
|
|
13507
|
+
}
|
|
13508
|
+
};
|
|
13509
|
+
}
|
|
13510
|
+
function defaultRandomId() {
|
|
13511
|
+
return randomBytes(6).toString("hex");
|
|
13512
|
+
}
|
|
13513
|
+
function defaultFormatLine(args) {
|
|
13514
|
+
const ctxFields = args.context ? Object.entries(args.context).filter(([, v]) => v !== undefined).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ") : "";
|
|
13515
|
+
const ctxPrefix = ctxFields ? ` ${ctxFields}` : "";
|
|
13516
|
+
return `${args.isoNow.toISOString()} PERF ${args.event} trace=${args.traceId} chatKey=${formatValue(args.chatKey)}${ctxPrefix} sinceStartMs=${args.sinceStartMs} sinceLastMs=${args.sinceLastMs}
|
|
13517
|
+
`;
|
|
13518
|
+
}
|
|
13519
|
+
function defaultFormatSummaryLine(args) {
|
|
13520
|
+
const extra = args.outcomeContext ? " " + Object.entries(args.outcomeContext).filter(([, v]) => v !== undefined).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ") : "";
|
|
13521
|
+
const marksJson = JSON.stringify(args.marks);
|
|
13522
|
+
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)}
|
|
13523
|
+
`;
|
|
13524
|
+
}
|
|
13525
|
+
function formatValue(value) {
|
|
13526
|
+
if (value === null)
|
|
13527
|
+
return "null";
|
|
13528
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
13529
|
+
return String(value);
|
|
13530
|
+
return JSON.stringify(value);
|
|
13531
|
+
}
|
|
13532
|
+
var NOOP_SPAN;
|
|
13533
|
+
var init_perf_tracer = __esm(() => {
|
|
13534
|
+
init_perf_log_writer();
|
|
13535
|
+
NOOP_SPAN = {
|
|
13536
|
+
traceId: "-",
|
|
13537
|
+
mark: () => {},
|
|
13538
|
+
setOutcome: () => {}
|
|
13539
|
+
};
|
|
13540
|
+
});
|
|
13541
|
+
|
|
13150
13542
|
// src/weixin/messaging/handle-weixin-message-turn.ts
|
|
13151
13543
|
import crypto4 from "node:crypto";
|
|
13152
13544
|
import fs8 from "node:fs/promises";
|
|
@@ -13278,6 +13670,9 @@ function isClearSlashCommand(textBody) {
|
|
|
13278
13670
|
const command = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
13279
13671
|
return command === "/clear";
|
|
13280
13672
|
}
|
|
13673
|
+
function isSlashCommandText(textBody) {
|
|
13674
|
+
return textBody.startsWith("/");
|
|
13675
|
+
}
|
|
13281
13676
|
function getWeixinMessageTurnLane(full) {
|
|
13282
13677
|
const textBody = extractTextBody(full.item_list).trim().toLowerCase();
|
|
13283
13678
|
return textBody === "/cancel" || textBody === "/stop" || textBody === "/jx" ? "control" : "normal";
|
|
@@ -13343,230 +13738,300 @@ async function handleWeixinMessageTurn(full, deps) {
|
|
|
13343
13738
|
}
|
|
13344
13739
|
}).catch(() => {});
|
|
13345
13740
|
};
|
|
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 {
|
|
13741
|
+
const chatKey = buildWeixinChatKey(deps.accountId, fromUserId);
|
|
13742
|
+
const initialMediaCount = extractWeixinMediaDescriptors(full.item_list).length;
|
|
13743
|
+
const isSlashCommand = isSlashCommandText(textBody);
|
|
13744
|
+
const tracer = deps.perfTracer ?? createNoopPerfTracer();
|
|
13745
|
+
return await tracer.wrapTurn({ chatKey, kind: isSlashCommand ? "command" : "prompt" }, async (perfSpan) => {
|
|
13746
|
+
perfSpan.mark("turn.received", {
|
|
13747
|
+
textLen: textBody.length,
|
|
13748
|
+
hasMedia: initialMediaCount > 0,
|
|
13749
|
+
mediaCount: initialMediaCount
|
|
13750
|
+
});
|
|
13751
|
+
const contextToken = full.context_token;
|
|
13752
|
+
if (contextToken) {
|
|
13753
|
+
setContextToken(deps.accountId, full.from_user_id ?? "", contextToken);
|
|
13754
|
+
}
|
|
13755
|
+
if (isSlashCommand) {
|
|
13756
|
+
const shouldTypeForSlash = isClearSlashCommand(textBody);
|
|
13375
13757
|
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;
|
|
13758
|
+
startTypingIndicator();
|
|
13399
13759
|
}
|
|
13400
13760
|
try {
|
|
13401
|
-
const
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13761
|
+
const slashResult = await handleSlashCommand(textBody, {
|
|
13762
|
+
to,
|
|
13763
|
+
contextToken: full.context_token,
|
|
13764
|
+
baseUrl: deps.baseUrl,
|
|
13765
|
+
token: deps.token,
|
|
13405
13766
|
accountId: deps.accountId,
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13767
|
+
log: deps.log,
|
|
13768
|
+
errLog: deps.errLog,
|
|
13769
|
+
onClear: () => deps.agent.clearSession?.(chatKey),
|
|
13770
|
+
...deps.hasPendingFinal ? { hasPendingFinal: deps.hasPendingFinal } : {},
|
|
13771
|
+
...deps.drainPendingFinal ? { drainPendingFinal: deps.drainPendingFinal } : {},
|
|
13772
|
+
...deps.prependPendingFinal ? { prependPendingFinal: deps.prependPendingFinal } : {},
|
|
13773
|
+
...deps.reserveFinal ? { reserveFinal: deps.reserveFinal } : {},
|
|
13774
|
+
...deps.finalRemaining ? { finalRemaining: deps.finalRemaining } : {}
|
|
13775
|
+
}, receivedAt, full.create_time_ms);
|
|
13776
|
+
if (slashResult.handled)
|
|
13777
|
+
return;
|
|
13414
13778
|
} finally {
|
|
13415
|
-
|
|
13779
|
+
if (shouldTypeForSlash) {
|
|
13780
|
+
stopTypingIndicator();
|
|
13781
|
+
}
|
|
13416
13782
|
}
|
|
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
13783
|
}
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
const
|
|
13424
|
-
|
|
13425
|
-
|
|
13784
|
+
startTypingIndicator();
|
|
13785
|
+
const mediaStore = deps.mediaStore ?? new RuntimeMediaStore({ rootDir: resolveMediaTempDir(deps.mediaTempDir) });
|
|
13786
|
+
const media = [];
|
|
13787
|
+
const attachmentNotes = [];
|
|
13788
|
+
const descriptors = extractWeixinMediaDescriptors(full.item_list).slice(0, DEFAULT_MAX_ATTACHMENTS_PER_MESSAGE);
|
|
13789
|
+
const download = deps.downloadMediaFromItemFn ?? downloadMediaFromItem;
|
|
13790
|
+
for (const descriptor of descriptors) {
|
|
13791
|
+
try {
|
|
13792
|
+
const downloaded = await download(descriptor.item, {
|
|
13793
|
+
cdnBaseUrl: deps.cdnBaseUrl,
|
|
13794
|
+
saveMedia: createSaveMediaBuffer(deps.mediaTempDir),
|
|
13795
|
+
log: deps.log,
|
|
13796
|
+
errLog: deps.errLog,
|
|
13797
|
+
label: "inbound"
|
|
13798
|
+
});
|
|
13799
|
+
const filePath = downloaded.decryptedPicPath ?? downloaded.decryptedVideoPath ?? downloaded.decryptedFilePath ?? downloaded.decryptedVoicePath;
|
|
13800
|
+
if (!filePath) {
|
|
13801
|
+
attachmentNotes.push(`Skipped ${descriptor.kind}: media was unavailable.`);
|
|
13802
|
+
continue;
|
|
13803
|
+
}
|
|
13804
|
+
try {
|
|
13805
|
+
const buffer = await fs8.readFile(filePath);
|
|
13806
|
+
const mimeType = downloaded.fileMediaType ?? downloaded.voiceMediaType ?? defaultWeixinMime(descriptor.kind);
|
|
13807
|
+
media.push(await mediaStore.saveMediaBuffer({
|
|
13808
|
+
channelId: "weixin",
|
|
13809
|
+
accountId: deps.accountId,
|
|
13810
|
+
chatKey: buildWeixinChatKey(deps.accountId, full.from_user_id ?? ""),
|
|
13811
|
+
messageId: full.message_id ? String(full.message_id) : full.context_token ?? String(full.create_time_ms ?? Date.now()),
|
|
13812
|
+
fileName: descriptor.fileName,
|
|
13813
|
+
mimeType,
|
|
13814
|
+
kind: descriptor.kind,
|
|
13815
|
+
buffer,
|
|
13816
|
+
maxBytes: descriptor.kind === "image" ? DEFAULT_IMAGE_MAX_BYTES : DEFAULT_ATTACHMENT_MAX_BYTES
|
|
13817
|
+
}));
|
|
13818
|
+
} finally {
|
|
13819
|
+
await fs8.rm(filePath, { force: true }).catch(() => {});
|
|
13820
|
+
}
|
|
13821
|
+
} catch (err) {
|
|
13822
|
+
deps.errLog(`media download failed: ${String(err)}`);
|
|
13823
|
+
attachmentNotes.push(`Skipped ${descriptor.kind}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13824
|
+
}
|
|
13426
13825
|
}
|
|
13826
|
+
let midFirstSent = false;
|
|
13827
|
+
const sendReplySegment = async (text) => {
|
|
13828
|
+
const plainText = markdownToPlainText(text).trim();
|
|
13829
|
+
if (plainText.length === 0) {
|
|
13830
|
+
return false;
|
|
13831
|
+
}
|
|
13832
|
+
try {
|
|
13833
|
+
await sendMessageWeixin({
|
|
13834
|
+
to,
|
|
13835
|
+
text: plainText,
|
|
13836
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13837
|
+
});
|
|
13838
|
+
if (!midFirstSent) {
|
|
13839
|
+
midFirstSent = true;
|
|
13840
|
+
perfSpan.mark("reply.mid_first_sent", { bytes: utf8ByteLength(plainText) });
|
|
13841
|
+
}
|
|
13842
|
+
return true;
|
|
13843
|
+
} catch (err) {
|
|
13844
|
+
deps.errLog(`intermediate reply failed: ${String(err)}`);
|
|
13845
|
+
return false;
|
|
13846
|
+
}
|
|
13847
|
+
};
|
|
13848
|
+
const requestText = appendAttachmentNotes(bodyFromItemList(full.item_list), attachmentNotes);
|
|
13849
|
+
const request = {
|
|
13850
|
+
accountId: deps.accountId,
|
|
13851
|
+
conversationId: buildWeixinChatKey(deps.accountId, full.from_user_id ?? ""),
|
|
13852
|
+
text: requestText,
|
|
13853
|
+
...media.length > 0 ? { media } : {},
|
|
13854
|
+
replyContextToken: contextToken,
|
|
13855
|
+
perfSpan
|
|
13856
|
+
};
|
|
13427
13857
|
try {
|
|
13428
|
-
await
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13858
|
+
const turn = await executeChatTurn({
|
|
13859
|
+
agent: deps.agent,
|
|
13860
|
+
request,
|
|
13861
|
+
onReplySegment: sendReplySegment
|
|
13432
13862
|
});
|
|
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) {
|
|
13863
|
+
const outboundMedia = normalizeMediaArray(turn.media);
|
|
13864
|
+
let finalFirstSent = false;
|
|
13865
|
+
let finalChunksSent = 0;
|
|
13866
|
+
let finalChunksPending = 0;
|
|
13867
|
+
let finalDropped = false;
|
|
13868
|
+
if (turn.text) {
|
|
13869
|
+
const finalText = markdownToPlainText(turn.text).trim();
|
|
13870
|
+
if (finalText.length > 0) {
|
|
13871
|
+
const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
|
|
13872
|
+
if (rawChunks.length > 0) {
|
|
13873
|
+
const total = rawChunks.length;
|
|
13874
|
+
if (total === 1) {
|
|
13488
13875
|
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13489
13876
|
if (!reserved) {
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
}
|
|
13493
|
-
try {
|
|
13877
|
+
finalDropped = true;
|
|
13878
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
|
|
13879
|
+
} else {
|
|
13494
13880
|
await sendMessageWeixin({
|
|
13495
13881
|
to,
|
|
13496
|
-
text:
|
|
13882
|
+
text: rawChunks[0],
|
|
13497
13883
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13498
13884
|
});
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13885
|
+
finalChunksSent += 1;
|
|
13886
|
+
if (!finalFirstSent) {
|
|
13887
|
+
finalFirstSent = true;
|
|
13888
|
+
perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(rawChunks[0]), chunkIndex: 1 });
|
|
13889
|
+
}
|
|
13890
|
+
}
|
|
13891
|
+
} else {
|
|
13892
|
+
const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
|
|
13893
|
+
const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
|
|
13894
|
+
const waveSize = Math.max(Math.min(available, total), 0);
|
|
13895
|
+
const wave = prefixed.slice(0, waveSize);
|
|
13896
|
+
const rest = prefixed.slice(waveSize);
|
|
13897
|
+
if (wave.length > 0 && rest.length > 0) {
|
|
13898
|
+
const sentSoFar = wave.length;
|
|
13899
|
+
wave[wave.length - 1] = `${wave[wave.length - 1]}
|
|
13900
|
+
|
|
13901
|
+
${buildFinalHeadsUp({
|
|
13902
|
+
total,
|
|
13903
|
+
sentSoFar
|
|
13904
|
+
})}`;
|
|
13905
|
+
}
|
|
13906
|
+
let sent = 0;
|
|
13907
|
+
for (let i = 0;i < wave.length; i += 1) {
|
|
13908
|
+
const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13909
|
+
if (!reserved) {
|
|
13910
|
+
finalDropped = true;
|
|
13911
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text_paginated chatKey=${to} chunk=${i + 1}/${total}`);
|
|
13912
|
+
break;
|
|
13913
|
+
}
|
|
13914
|
+
try {
|
|
13915
|
+
await sendMessageWeixin({
|
|
13916
|
+
to,
|
|
13917
|
+
text: wave[i],
|
|
13918
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
|
|
13919
|
+
});
|
|
13920
|
+
sent += 1;
|
|
13921
|
+
finalChunksSent += 1;
|
|
13922
|
+
if (!finalFirstSent) {
|
|
13923
|
+
finalFirstSent = true;
|
|
13924
|
+
perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(wave[i]), chunkIndex: i + 1 });
|
|
13925
|
+
}
|
|
13926
|
+
} catch (sendErr) {
|
|
13927
|
+
finalDropped = true;
|
|
13928
|
+
deps.errLog(`weixin.final.dropped reason=send_failed kind=text_paginated chatKey=${to} chunk=${i + 1}/${total} err=${String(sendErr)}`);
|
|
13929
|
+
break;
|
|
13930
|
+
}
|
|
13931
|
+
}
|
|
13932
|
+
const restToPark = prefixed.slice(sent);
|
|
13933
|
+
finalChunksPending = restToPark.length;
|
|
13934
|
+
if (restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
13935
|
+
const pending = restToPark.map((text, idx) => {
|
|
13936
|
+
const seq = sent + idx + 1;
|
|
13937
|
+
const entry = { text, seq, total };
|
|
13938
|
+
if (contextToken !== undefined)
|
|
13939
|
+
entry.contextToken = contextToken;
|
|
13940
|
+
if (deps.accountId !== undefined)
|
|
13941
|
+
entry.accountId = deps.accountId;
|
|
13942
|
+
return entry;
|
|
13943
|
+
});
|
|
13944
|
+
deps.enqueuePendingFinal(to, pending);
|
|
13503
13945
|
}
|
|
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
13946
|
}
|
|
13518
13947
|
}
|
|
13519
13948
|
}
|
|
13949
|
+
perfSpan.mark("reply.final_done", {
|
|
13950
|
+
chunksSent: finalChunksSent,
|
|
13951
|
+
chunksPending: finalChunksPending,
|
|
13952
|
+
dropped: finalDropped
|
|
13953
|
+
});
|
|
13520
13954
|
}
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13955
|
+
let mediaSent = 0;
|
|
13956
|
+
let mediaFailed = 0;
|
|
13957
|
+
let mediaRejected = 0;
|
|
13958
|
+
let mediaDropped = 0;
|
|
13959
|
+
for (const mediaItem of outboundMedia) {
|
|
13960
|
+
const filePath = await resolveSafeOutboundMediaPath(mediaItem.filePath, [mediaStore.rootDir, resolveMediaTempDir(deps.mediaTempDir), ...deps.allowedMediaRoots ?? []]);
|
|
13961
|
+
if (!filePath) {
|
|
13962
|
+
mediaRejected += 1;
|
|
13963
|
+
deps.errLog(`outbound media rejected: path=${mediaItem.filePath}`);
|
|
13964
|
+
continue;
|
|
13965
|
+
}
|
|
13966
|
+
const caption = mediaItem.caption ? markdownToPlainText(mediaItem.caption) : "";
|
|
13967
|
+
const captionReserve = caption && deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13968
|
+
if (!captionReserve) {
|
|
13969
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=media_caption chatKey=${to}`);
|
|
13970
|
+
}
|
|
13971
|
+
const reservedMedia = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
13972
|
+
if (!reservedMedia) {
|
|
13973
|
+
mediaDropped += 1;
|
|
13974
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=media chatKey=${to}`);
|
|
13975
|
+
continue;
|
|
13976
|
+
}
|
|
13977
|
+
try {
|
|
13978
|
+
const sent = await sendWeixinMediaFile({
|
|
13979
|
+
media: mediaItem,
|
|
13980
|
+
filePath,
|
|
13981
|
+
to,
|
|
13982
|
+
text: captionReserve ? caption : "",
|
|
13983
|
+
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken },
|
|
13984
|
+
cdnBaseUrl: deps.cdnBaseUrl
|
|
13985
|
+
});
|
|
13986
|
+
mediaSent += 1;
|
|
13987
|
+
perfSpan.mark("reply.media_sent", {
|
|
13988
|
+
kind: mediaItem.kind,
|
|
13989
|
+
index: mediaSent + mediaFailed + mediaRejected + mediaDropped,
|
|
13990
|
+
messageId: sent.messageId
|
|
13991
|
+
});
|
|
13992
|
+
} catch (err) {
|
|
13993
|
+
mediaFailed += 1;
|
|
13994
|
+
deps.errLog(`outbound media send failed: ${String(err)}`);
|
|
13995
|
+
}
|
|
13527
13996
|
}
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13997
|
+
if (outboundMedia.length > 0) {
|
|
13998
|
+
perfSpan.mark("reply.media_done", {
|
|
13999
|
+
mediaCount: outboundMedia.length,
|
|
14000
|
+
sent: mediaSent,
|
|
14001
|
+
failed: mediaFailed,
|
|
14002
|
+
rejected: mediaRejected,
|
|
14003
|
+
dropped: mediaDropped
|
|
14004
|
+
});
|
|
13532
14005
|
}
|
|
13533
|
-
|
|
13534
|
-
if (
|
|
13535
|
-
|
|
13536
|
-
|
|
14006
|
+
} catch (err) {
|
|
14007
|
+
if (isAbortError(err)) {
|
|
14008
|
+
perfSpan.setOutcome("aborted", { reason: "user_cancel" });
|
|
14009
|
+
deps.log(`handleWeixinMessageTurn: turn aborted: ${err.message}`);
|
|
14010
|
+
return;
|
|
13537
14011
|
}
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
14012
|
+
perfSpan.setOutcome("error", { reason: "turn_error" });
|
|
14013
|
+
const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
|
|
14014
|
+
deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
|
|
14015
|
+
const reservedErr = deps.reserveFinal ? deps.reserveFinal(to) : true;
|
|
14016
|
+
if (!reservedErr) {
|
|
14017
|
+
deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=error_notice chatKey=${to}`);
|
|
14018
|
+
} else {
|
|
14019
|
+
sendWeixinErrorNotice({
|
|
13542
14020
|
to,
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
14021
|
+
contextToken,
|
|
14022
|
+
message: `⚠️ 过程失败:${err instanceof Error ? err.message : JSON.stringify(err)}`,
|
|
14023
|
+
baseUrl: deps.baseUrl,
|
|
14024
|
+
token: deps.token,
|
|
14025
|
+
errLog: deps.errLog
|
|
13546
14026
|
});
|
|
13547
|
-
} catch (err) {
|
|
13548
|
-
deps.errLog(`outbound media send failed: ${String(err)}`);
|
|
13549
14027
|
}
|
|
14028
|
+
} finally {
|
|
14029
|
+
stopTypingIndicator();
|
|
13550
14030
|
}
|
|
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
|
-
}
|
|
14031
|
+
});
|
|
14032
|
+
}
|
|
14033
|
+
function isAbortError(error2) {
|
|
14034
|
+
return error2 instanceof Error && error2.name === "AbortError";
|
|
13570
14035
|
}
|
|
13571
14036
|
var MAX_FINAL_CHUNK_BYTES = 1800;
|
|
13572
14037
|
var init_handle_weixin_message_turn = __esm(() => {
|
|
@@ -13581,6 +14046,7 @@ var init_handle_weixin_message_turn = __esm(() => {
|
|
|
13581
14046
|
init_send_media();
|
|
13582
14047
|
init_send();
|
|
13583
14048
|
init_slash_commands();
|
|
14049
|
+
init_perf_tracer();
|
|
13584
14050
|
});
|
|
13585
14051
|
|
|
13586
14052
|
// src/weixin/storage/sync-buf.ts
|
|
@@ -13769,7 +14235,8 @@ async function monitorWeixinProvider(opts) {
|
|
|
13769
14235
|
...opts.drainPendingFinal ? { drainPendingFinal: opts.drainPendingFinal } : {},
|
|
13770
14236
|
...opts.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
|
|
13771
14237
|
...opts.mediaStore ? { mediaStore: opts.mediaStore } : {},
|
|
13772
|
-
...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {}
|
|
14238
|
+
...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {},
|
|
14239
|
+
...opts.perfTracer ? { perfTracer: opts.perfTracer } : {}
|
|
13773
14240
|
})).catch((err) => {
|
|
13774
14241
|
errLog(`[weixin] message turn failed: ${String(err)}`);
|
|
13775
14242
|
});
|
|
@@ -13912,7 +14379,8 @@ async function start(agent, opts) {
|
|
|
13912
14379
|
...opts?.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
|
|
13913
14380
|
...opts?.enqueuePendingFinal ? { enqueuePendingFinal: opts.enqueuePendingFinal } : {},
|
|
13914
14381
|
...opts?.dropPendingFinal ? { dropPendingFinal: opts.dropPendingFinal } : {},
|
|
13915
|
-
...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {}
|
|
14382
|
+
...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {},
|
|
14383
|
+
...opts?.perfTracer ? { perfTracer: opts.perfTracer } : {}
|
|
13916
14384
|
});
|
|
13917
14385
|
}
|
|
13918
14386
|
var init_bot = __esm(() => {
|
|
@@ -14168,16 +14636,16 @@ var init_deliver_coordinator_message = __esm(() => {
|
|
|
14168
14636
|
});
|
|
14169
14637
|
|
|
14170
14638
|
// src/weixin/monitor/consumer-lock.ts
|
|
14171
|
-
import { mkdir as mkdir8, open as open2, readFile as readFile6, rm as
|
|
14172
|
-
import { dirname as
|
|
14639
|
+
import { mkdir as mkdir8, open as open2, readFile as readFile6, rm as rm6 } from "node:fs/promises";
|
|
14640
|
+
import { dirname as dirname8, join as join5 } from "node:path";
|
|
14173
14641
|
import { homedir as homedir4 } from "node:os";
|
|
14174
14642
|
function createWeixinConsumerLock(options = {}) {
|
|
14175
|
-
const lockFilePath = options.lockFilePath ??
|
|
14643
|
+
const lockFilePath = options.lockFilePath ?? join5(homedir4(), ".weacpx", "runtime", "weixin-consumer.lock.json");
|
|
14176
14644
|
const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
|
|
14177
14645
|
const onDiagnostic = options.onDiagnostic;
|
|
14178
14646
|
return {
|
|
14179
14647
|
async acquire(meta2) {
|
|
14180
|
-
await mkdir8(
|
|
14648
|
+
await mkdir8(dirname8(lockFilePath), { recursive: true });
|
|
14181
14649
|
while (true) {
|
|
14182
14650
|
try {
|
|
14183
14651
|
const handle = await open2(lockFilePath, "wx");
|
|
@@ -14208,7 +14676,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14208
14676
|
});
|
|
14209
14677
|
const existing = await loadLockMetadata(lockFilePath);
|
|
14210
14678
|
if (!existing) {
|
|
14211
|
-
await
|
|
14679
|
+
await rm6(lockFilePath, { force: true });
|
|
14212
14680
|
await onDiagnostic?.("lock_invalid_removed", {
|
|
14213
14681
|
lockFilePath,
|
|
14214
14682
|
reason: "invalid_or_unreadable_metadata"
|
|
@@ -14216,7 +14684,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14216
14684
|
continue;
|
|
14217
14685
|
}
|
|
14218
14686
|
if (!isProcessRunning(existing.pid)) {
|
|
14219
|
-
await
|
|
14687
|
+
await rm6(lockFilePath, { force: true });
|
|
14220
14688
|
await onDiagnostic?.("lock_stale_removed", {
|
|
14221
14689
|
lockFilePath,
|
|
14222
14690
|
stalePid: existing.pid,
|
|
@@ -14241,7 +14709,7 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
14241
14709
|
}
|
|
14242
14710
|
},
|
|
14243
14711
|
async release() {
|
|
14244
|
-
await
|
|
14712
|
+
await rm6(lockFilePath, { force: true });
|
|
14245
14713
|
await onDiagnostic?.("lock_released", {
|
|
14246
14714
|
lockFilePath
|
|
14247
14715
|
});
|
|
@@ -14340,7 +14808,8 @@ class WeixinChannel {
|
|
|
14340
14808
|
drainPendingFinal: (chatKey, available) => input.quota.drainPendingFinalUpToBudget(chatKey, available),
|
|
14341
14809
|
prependPendingFinal: (chatKey, chunks) => input.quota.prependPendingFinal(chatKey, chunks),
|
|
14342
14810
|
enqueuePendingFinal: (chatKey, chunks) => input.quota.enqueuePendingFinal(chatKey, chunks),
|
|
14343
|
-
dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey)
|
|
14811
|
+
dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey),
|
|
14812
|
+
...input.perfTracer ? { perfTracer: input.perfTracer } : {}
|
|
14344
14813
|
});
|
|
14345
14814
|
}
|
|
14346
14815
|
async notifyTaskCompletion(task) {
|
|
@@ -14849,9 +15318,9 @@ __export(exports_plugin_loader, {
|
|
|
14849
15318
|
});
|
|
14850
15319
|
import { createRequire as createRequire2 } from "node:module";
|
|
14851
15320
|
import { pathToFileURL } from "node:url";
|
|
14852
|
-
import { join as
|
|
15321
|
+
import { join as join6 } from "node:path";
|
|
14853
15322
|
async function importPluginFromHome(packageName, pluginHome) {
|
|
14854
|
-
const requireFromHome = createRequire2(
|
|
15323
|
+
const requireFromHome = createRequire2(join6(pluginHome, "package.json"));
|
|
14855
15324
|
const entry = requireFromHome.resolve(packageName);
|
|
14856
15325
|
return await import(pathToFileURL(entry).href);
|
|
14857
15326
|
}
|
|
@@ -14899,8 +15368,8 @@ var init_bootstrap = __esm(() => {
|
|
|
14899
15368
|
});
|
|
14900
15369
|
|
|
14901
15370
|
// src/logging/app-logger.ts
|
|
14902
|
-
import { appendFile, mkdir as mkdir9
|
|
14903
|
-
import {
|
|
15371
|
+
import { appendFile, mkdir as mkdir9 } from "node:fs/promises";
|
|
15372
|
+
import { dirname as dirname10 } from "node:path";
|
|
14904
15373
|
function createNoopAppLogger() {
|
|
14905
15374
|
return {
|
|
14906
15375
|
debug: async () => {},
|
|
@@ -14940,74 +15409,18 @@ function createAppLogger(options) {
|
|
|
14940
15409
|
return;
|
|
14941
15410
|
}
|
|
14942
15411
|
const line = formatLogLine(now(), level, event, message, context);
|
|
14943
|
-
await mkdir9(
|
|
15412
|
+
await mkdir9(dirname10(options.filePath), { recursive: true });
|
|
14944
15413
|
await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
|
|
14945
15414
|
await appendFile(options.filePath, line, "utf8");
|
|
14946
15415
|
}
|
|
14947
15416
|
}
|
|
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
15417
|
function formatLogLine(time3, level, event, message, context) {
|
|
15005
|
-
const fields = Object.entries(context).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${
|
|
15418
|
+
const fields = Object.entries(context).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${formatValue2(value)}`);
|
|
15006
15419
|
const suffix = fields.length > 0 ? ` ${fields.join(" ")}` : "";
|
|
15007
|
-
return `${time3.toISOString()} ${level.toUpperCase()} ${event} message=${
|
|
15420
|
+
return `${time3.toISOString()} ${level.toUpperCase()} ${event} message=${formatValue2(message)}${suffix}
|
|
15008
15421
|
`;
|
|
15009
15422
|
}
|
|
15010
|
-
function
|
|
15423
|
+
function formatValue2(value) {
|
|
15011
15424
|
if (value === null) {
|
|
15012
15425
|
return "null";
|
|
15013
15426
|
}
|
|
@@ -15016,11 +15429,9 @@ function formatValue(value) {
|
|
|
15016
15429
|
}
|
|
15017
15430
|
return JSON.stringify(value);
|
|
15018
15431
|
}
|
|
15019
|
-
function isMissingFileError2(error2) {
|
|
15020
|
-
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
15021
|
-
}
|
|
15022
15432
|
var LEVEL_ORDER;
|
|
15023
15433
|
var init_app_logger = __esm(() => {
|
|
15434
|
+
init_rotating_file_writer();
|
|
15024
15435
|
LEVEL_ORDER = {
|
|
15025
15436
|
error: 0,
|
|
15026
15437
|
info: 1,
|
|
@@ -15091,38 +15502,24 @@ function extractPromptFailureMessage(result) {
|
|
|
15091
15502
|
function extractPromptOutput(output) {
|
|
15092
15503
|
const lines = output.split(`
|
|
15093
15504
|
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
15094
|
-
|
|
15095
|
-
let currentSegment = "";
|
|
15505
|
+
let text = "";
|
|
15096
15506
|
let hasAgentMessage = false;
|
|
15097
15507
|
for (const line of lines) {
|
|
15508
|
+
let event;
|
|
15098
15509
|
try {
|
|
15099
|
-
|
|
15100
|
-
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
15101
|
-
if (isMessageChunk) {
|
|
15102
|
-
hasAgentMessage = true;
|
|
15103
|
-
const chunk = event.params.update.content.text ?? "";
|
|
15104
|
-
if (chunk.length > 0) {
|
|
15105
|
-
currentSegment += chunk;
|
|
15106
|
-
}
|
|
15107
|
-
continue;
|
|
15108
|
-
}
|
|
15109
|
-
if (currentSegment.trim().length > 0) {
|
|
15110
|
-
messageSegments.push(currentSegment.trim());
|
|
15111
|
-
}
|
|
15112
|
-
currentSegment = "";
|
|
15510
|
+
event = JSON.parse(line);
|
|
15113
15511
|
} catch {
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
|
|
15117
|
-
|
|
15512
|
+
continue;
|
|
15513
|
+
}
|
|
15514
|
+
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
15515
|
+
if (isMessageChunk) {
|
|
15516
|
+
hasAgentMessage = true;
|
|
15517
|
+
text += event.params.update.content.text ?? "";
|
|
15118
15518
|
}
|
|
15119
15519
|
}
|
|
15120
|
-
if (
|
|
15121
|
-
messageSegments.push(currentSegment.trim());
|
|
15122
|
-
}
|
|
15123
|
-
if (messageSegments.length > 0) {
|
|
15520
|
+
if (hasAgentMessage && text.trim().length > 0) {
|
|
15124
15521
|
return {
|
|
15125
|
-
text:
|
|
15522
|
+
text: text.trim(),
|
|
15126
15523
|
hasAgentMessage
|
|
15127
15524
|
};
|
|
15128
15525
|
}
|
|
@@ -16440,6 +16837,7 @@ async function handleSessionAttach(context, chatKey, alias, agent, workspace, tr
|
|
|
16440
16837
|
`)
|
|
16441
16838
|
};
|
|
16442
16839
|
}
|
|
16840
|
+
context.lifecycle.markSessionReady?.(attached);
|
|
16443
16841
|
await context.sessions.attachSession(internalAlias, agent, workspace, transportSession);
|
|
16444
16842
|
await context.sessions.useSession(chatKey, internalAlias);
|
|
16445
16843
|
await refreshSessionTransportAgentCommandBestEffort(context, internalAlias, "session.attach.agent_command_refresh_failed");
|
|
@@ -16636,7 +17034,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
16636
17034
|
return { text: lines.join(`
|
|
16637
17035
|
`) };
|
|
16638
17036
|
}
|
|
16639
|
-
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
|
|
17037
|
+
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
16640
17038
|
const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
|
|
16641
17039
|
if (!session.replyMode)
|
|
16642
17040
|
session.replyMode = effectiveReplyMode;
|
|
@@ -16661,7 +17059,7 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
16661
17059
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
|
|
16662
17060
|
try {
|
|
16663
17061
|
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);
|
|
17062
|
+
const result = await context.interaction.promptTransportSession(session, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, perfSpan);
|
|
16665
17063
|
if (claimHumanReply) {
|
|
16666
17064
|
try {
|
|
16667
17065
|
await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
|
|
@@ -16681,17 +17079,17 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
16681
17079
|
throw error2;
|
|
16682
17080
|
}
|
|
16683
17081
|
}
|
|
16684
|
-
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent) {
|
|
17082
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan) {
|
|
16685
17083
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
16686
17084
|
if (!session) {
|
|
16687
17085
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
16688
17086
|
}
|
|
16689
17087
|
try {
|
|
16690
|
-
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
17088
|
+
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
16691
17089
|
} catch (error2) {
|
|
16692
17090
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
|
|
16693
17091
|
if (recovered) {
|
|
16694
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
17092
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
16695
17093
|
}
|
|
16696
17094
|
return context.recovery.renderTransportError(session, error2);
|
|
16697
17095
|
}
|
|
@@ -16921,6 +17319,8 @@ function renderTaskSummary2(task) {
|
|
|
16921
17319
|
header.push(`- 任务:${task.task}`);
|
|
16922
17320
|
if (task.summary.trim().length > 0)
|
|
16923
17321
|
header.push(`- 摘要:${task.summary}`);
|
|
17322
|
+
if (task.lastProgressSummary)
|
|
17323
|
+
header.push(`- 最新进展:${task.lastProgressSummary}`);
|
|
16924
17324
|
if (task.resultText.trim().length > 0)
|
|
16925
17325
|
header.push(`- 结果:${task.resultText}`);
|
|
16926
17326
|
const events = [];
|
|
@@ -17364,6 +17764,13 @@ async function handleAgentAdd(context, templateName) {
|
|
|
17364
17764
|
if (!template) {
|
|
17365
17765
|
return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
|
|
17366
17766
|
}
|
|
17767
|
+
const existing = context.config.agents[templateName];
|
|
17768
|
+
if (existing) {
|
|
17769
|
+
if (sameAgentConfig(existing, template)) {
|
|
17770
|
+
return { text: `Agent「${templateName}」已存在` };
|
|
17771
|
+
}
|
|
17772
|
+
return { text: `Agent「${templateName}」已存在且配置不同。请先执行 /agent rm ${templateName}` };
|
|
17773
|
+
}
|
|
17367
17774
|
const updated = await context.configStore.upsertAgent(templateName, template);
|
|
17368
17775
|
context.replaceConfig(updated);
|
|
17369
17776
|
return { text: `Agent「${templateName}」已保存` };
|
|
@@ -17388,7 +17795,7 @@ var init_agent_handler = __esm(() => {
|
|
|
17388
17795
|
summary: "管理已注册的 Agent。",
|
|
17389
17796
|
commands: [
|
|
17390
17797
|
{ usage: "/agents", description: "查看当前已注册的 Agent" },
|
|
17391
|
-
{ usage:
|
|
17798
|
+
{ usage: `/agent add <${listAgentTemplates().join("|")}>`, description: "添加内置 Agent 模板" },
|
|
17392
17799
|
{ usage: "/agent rm <name>", description: "删除一个 Agent" }
|
|
17393
17800
|
],
|
|
17394
17801
|
examples: ["/agent add claude", "/agent rm codex"]
|
|
@@ -17955,7 +18362,7 @@ import { spawn as spawn6 } from "node:child_process";
|
|
|
17955
18362
|
import { createRequire as createRequire3 } from "node:module";
|
|
17956
18363
|
import { access as access3 } from "node:fs/promises";
|
|
17957
18364
|
import { homedir as homedir7 } from "node:os";
|
|
17958
|
-
import { dirname as
|
|
18365
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
17959
18366
|
function deriveParentPackageName(platformPackage) {
|
|
17960
18367
|
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
17961
18368
|
}
|
|
@@ -18028,7 +18435,7 @@ function defaultResolveFromCwd(name, cwd) {
|
|
|
18028
18435
|
const pkgJson = require2.resolve(`${name}/package.json`, {
|
|
18029
18436
|
paths: [cwd, ...require2.resolve.paths(name) ?? []]
|
|
18030
18437
|
});
|
|
18031
|
-
return
|
|
18438
|
+
return dirname11(pkgJson);
|
|
18032
18439
|
} catch {
|
|
18033
18440
|
return null;
|
|
18034
18441
|
}
|
|
@@ -18186,7 +18593,7 @@ class CommandRouter {
|
|
|
18186
18593
|
this.quota = quota;
|
|
18187
18594
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
18188
18595
|
}
|
|
18189
|
-
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent) {
|
|
18596
|
+
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, perfSpan) {
|
|
18190
18597
|
const startedAt = Date.now();
|
|
18191
18598
|
const command = parseCommand(input);
|
|
18192
18599
|
await this.logger.debug("command.parsed", "parsed inbound command", {
|
|
@@ -18194,6 +18601,7 @@ class CommandRouter {
|
|
|
18194
18601
|
kind: command.kind
|
|
18195
18602
|
});
|
|
18196
18603
|
const access4 = authorizeCommandForChat(command, metadata);
|
|
18604
|
+
perfSpan?.mark("router.authorized", { decision: access4.allowed ? "allow" : "deny" });
|
|
18197
18605
|
if (!access4.allowed) {
|
|
18198
18606
|
await this.logger.info("command.blocked", "blocked command by chat policy", {
|
|
18199
18607
|
chatKey,
|
|
@@ -18205,6 +18613,7 @@ class CommandRouter {
|
|
|
18205
18613
|
return { text: renderCommandAccessDenied(command) };
|
|
18206
18614
|
}
|
|
18207
18615
|
await this.refreshConfigFromStore();
|
|
18616
|
+
perfSpan?.mark("router.config_refreshed");
|
|
18208
18617
|
return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
|
|
18209
18618
|
switch (command.kind) {
|
|
18210
18619
|
case "invalid":
|
|
@@ -18247,35 +18656,35 @@ class CommandRouter {
|
|
|
18247
18656
|
case "workspace.rm":
|
|
18248
18657
|
return await handleWorkspaceRemove(this.createHandlerContext(), command.name);
|
|
18249
18658
|
case "sessions":
|
|
18250
|
-
return await handleSessions(this.createSessionHandlerContext(), chatKey);
|
|
18659
|
+
return await handleSessions(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18251
18660
|
case "session.new":
|
|
18252
|
-
return await handleSessionNew(this.createSessionHandlerContext(reply), chatKey, command.alias, command.agent, command.workspace);
|
|
18661
|
+
return await handleSessionNew(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace);
|
|
18253
18662
|
case "session.shortcut":
|
|
18254
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, false);
|
|
18663
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, false);
|
|
18255
18664
|
case "session.shortcut.new":
|
|
18256
|
-
return await handleSessionShortcut(this.createSessionHandlerContext(reply), chatKey, command.agent, command, true);
|
|
18665
|
+
return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
|
|
18257
18666
|
case "session.attach":
|
|
18258
|
-
return await handleSessionAttach(this.createSessionHandlerContext(reply), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
|
|
18667
|
+
return await handleSessionAttach(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
|
|
18259
18668
|
case "session.use":
|
|
18260
|
-
return await handleSessionUse(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18669
|
+
return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18261
18670
|
case "mode.show":
|
|
18262
|
-
return await handleModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18671
|
+
return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18263
18672
|
case "mode.set":
|
|
18264
|
-
return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
|
|
18673
|
+
return await handleModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.modeId);
|
|
18265
18674
|
case "replymode.show":
|
|
18266
|
-
return await handleReplyModeShow(this.createSessionHandlerContext(), chatKey);
|
|
18675
|
+
return await handleReplyModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18267
18676
|
case "replymode.set":
|
|
18268
|
-
return await handleReplyModeSet(this.createSessionHandlerContext(), chatKey, command.replyMode);
|
|
18677
|
+
return await handleReplyModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.replyMode);
|
|
18269
18678
|
case "replymode.reset":
|
|
18270
|
-
return await handleReplyModeReset(this.createSessionHandlerContext(), chatKey);
|
|
18679
|
+
return await handleReplyModeReset(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18271
18680
|
case "status":
|
|
18272
|
-
return await handleStatus(this.createSessionHandlerContext(), chatKey);
|
|
18681
|
+
return await handleStatus(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18273
18682
|
case "cancel":
|
|
18274
|
-
return await handleCancel(this.createSessionHandlerContext(), chatKey);
|
|
18683
|
+
return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
|
|
18275
18684
|
case "session.reset":
|
|
18276
|
-
return await handleSessionReset(this.createSessionHandlerContext(reply), chatKey);
|
|
18685
|
+
return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
|
|
18277
18686
|
case "session.rm":
|
|
18278
|
-
return await handleSessionRemove(this.createSessionHandlerContext(), chatKey, command.alias);
|
|
18687
|
+
return await handleSessionRemove(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
|
|
18279
18688
|
case "groups":
|
|
18280
18689
|
return await handleGroupList(this.createHandlerContext(), chatKey, command.filter);
|
|
18281
18690
|
case "group.new":
|
|
@@ -18301,7 +18710,7 @@ class CommandRouter {
|
|
|
18301
18710
|
case "task.cancel":
|
|
18302
18711
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
18303
18712
|
case "prompt":
|
|
18304
|
-
return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent);
|
|
18713
|
+
return await handlePrompt(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, perfSpan);
|
|
18305
18714
|
}
|
|
18306
18715
|
});
|
|
18307
18716
|
}
|
|
@@ -18320,23 +18729,24 @@ class CommandRouter {
|
|
|
18320
18729
|
...this.quota ? { quota: this.quota } : {}
|
|
18321
18730
|
};
|
|
18322
18731
|
}
|
|
18323
|
-
createSessionHandlerContext(reply) {
|
|
18732
|
+
createSessionHandlerContext(reply, perfSpan) {
|
|
18324
18733
|
return {
|
|
18325
18734
|
...this.createHandlerContext(),
|
|
18326
|
-
lifecycle: this.createSessionLifecycleOps(reply),
|
|
18327
|
-
interaction: this.createSessionInteractionOps(),
|
|
18735
|
+
lifecycle: this.createSessionLifecycleOps(reply, perfSpan),
|
|
18736
|
+
interaction: this.createSessionInteractionOps(perfSpan),
|
|
18328
18737
|
recovery: this.createSessionRenderRecoveryOps()
|
|
18329
18738
|
};
|
|
18330
18739
|
}
|
|
18331
|
-
createSessionLifecycleOps(reply) {
|
|
18740
|
+
createSessionLifecycleOps(reply, perfSpan) {
|
|
18332
18741
|
return {
|
|
18333
18742
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18334
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18743
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18335
18744
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18745
|
+
markSessionReady: () => perfSpan?.mark("session.ready"),
|
|
18336
18746
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18337
18747
|
handleSessionShortcut: async (chatKey, agent, target, createNew, replyOverride) => {
|
|
18338
18748
|
try {
|
|
18339
|
-
return await handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(replyOverride ?? reply), chatKey, agent, target, createNew);
|
|
18749
|
+
return await handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(replyOverride ?? reply, perfSpan), chatKey, agent, target, createNew);
|
|
18340
18750
|
} catch (err) {
|
|
18341
18751
|
if (err instanceof AutoInstallFailedError) {
|
|
18342
18752
|
const session = this.sessions.resolveSession(`${agent}`, agent, target.workspace ?? "", `${agent}`);
|
|
@@ -18345,15 +18755,15 @@ class CommandRouter {
|
|
|
18345
18755
|
throw err;
|
|
18346
18756
|
}
|
|
18347
18757
|
},
|
|
18348
|
-
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply), chatKey),
|
|
18758
|
+
resetCurrentSession: (chatKey, replyOverride) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(replyOverride ?? reply, perfSpan), chatKey),
|
|
18349
18759
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
18350
18760
|
};
|
|
18351
18761
|
}
|
|
18352
|
-
createSessionInteractionOps() {
|
|
18762
|
+
createSessionInteractionOps(perfSpan) {
|
|
18353
18763
|
return {
|
|
18354
18764
|
setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
|
|
18355
18765
|
cancelTransportSession: (session) => this.cancelTransportSession(session),
|
|
18356
|
-
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent)
|
|
18766
|
+
promptTransportSession: (session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride) => this.promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpanOverride ?? perfSpan)
|
|
18357
18767
|
};
|
|
18358
18768
|
}
|
|
18359
18769
|
createSessionRenderRecoveryOps() {
|
|
@@ -18364,9 +18774,9 @@ class CommandRouter {
|
|
|
18364
18774
|
renderTransportError: (session, error2) => renderTransportError(session, error2)
|
|
18365
18775
|
};
|
|
18366
18776
|
}
|
|
18367
|
-
createSessionResetOps(reply) {
|
|
18777
|
+
createSessionResetOps(reply, perfSpan) {
|
|
18368
18778
|
return {
|
|
18369
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18779
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18370
18780
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18371
18781
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18372
18782
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
@@ -18381,10 +18791,10 @@ class CommandRouter {
|
|
|
18381
18791
|
getSession: (alias) => this.sessions.getSession(alias)
|
|
18382
18792
|
};
|
|
18383
18793
|
}
|
|
18384
|
-
createSessionShortcutOps(reply) {
|
|
18794
|
+
createSessionShortcutOps(reply, perfSpan) {
|
|
18385
18795
|
return {
|
|
18386
18796
|
resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
|
|
18387
|
-
ensureTransportSession: (session, replyOverride) => this.ensureTransportSession(session, replyOverride ?? reply),
|
|
18797
|
+
ensureTransportSession: (session, replyOverride, perfSpanOverride) => this.ensureTransportSession(session, replyOverride ?? reply, perfSpanOverride ?? perfSpan),
|
|
18388
18798
|
checkTransportSession: (session) => this.checkTransportSession(session),
|
|
18389
18799
|
reserveTransportSession: (transportSession) => this.reserveLogicalTransportSession(transportSession),
|
|
18390
18800
|
refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
|
|
@@ -18445,13 +18855,14 @@ class CommandRouter {
|
|
|
18445
18855
|
throw error2;
|
|
18446
18856
|
}
|
|
18447
18857
|
}
|
|
18448
|
-
async ensureTransportSession(session, reply) {
|
|
18858
|
+
async ensureTransportSession(session, reply, perfSpan) {
|
|
18449
18859
|
const attemptSession = (operation) => {
|
|
18450
18860
|
const { handler, dispose } = this.createProgressHandler(session, reply);
|
|
18451
18861
|
return this.measureTransportCall(operation, session, () => this.transport.ensureSession(session, handler)).finally(dispose);
|
|
18452
18862
|
};
|
|
18453
18863
|
try {
|
|
18454
18864
|
await attemptSession("ensure_session");
|
|
18865
|
+
perfSpan?.mark("session.ready");
|
|
18455
18866
|
} catch (err) {
|
|
18456
18867
|
if (!(err instanceof MissingOptionalDepError))
|
|
18457
18868
|
throw err;
|
|
@@ -18464,6 +18875,7 @@ class CommandRouter {
|
|
|
18464
18875
|
await reply?.(`\uD83D\uDD04 安装完成,正在验证会话启动…`);
|
|
18465
18876
|
try {
|
|
18466
18877
|
await attemptSession("ensure_session.verify");
|
|
18878
|
+
perfSpan?.mark("session.ready");
|
|
18467
18879
|
return true;
|
|
18468
18880
|
} catch (retryErr) {
|
|
18469
18881
|
if (retryErr instanceof MissingOptionalDepError)
|
|
@@ -18529,11 +18941,13 @@ class CommandRouter {
|
|
|
18529
18941
|
async checkTransportSession(session) {
|
|
18530
18942
|
return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
|
|
18531
18943
|
}
|
|
18532
|
-
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent) {
|
|
18944
|
+
async promptTransportSession(session, text, reply, replyContext, media, abortSignal, onToolEvent, perfSpan) {
|
|
18533
18945
|
session.mcpCoordinatorSession ??= session.transportSession;
|
|
18534
18946
|
let done = false;
|
|
18947
|
+
let abortRequested = false;
|
|
18535
18948
|
let cancelOnAbort;
|
|
18536
18949
|
const fireCancel = () => {
|
|
18950
|
+
abortRequested = true;
|
|
18537
18951
|
if (done)
|
|
18538
18952
|
return;
|
|
18539
18953
|
try {
|
|
@@ -18557,20 +18971,42 @@ class CommandRouter {
|
|
|
18557
18971
|
});
|
|
18558
18972
|
}
|
|
18559
18973
|
};
|
|
18974
|
+
let localOutcome = "ok";
|
|
18560
18975
|
if (abortSignal) {
|
|
18561
18976
|
if (abortSignal.aborted) {
|
|
18562
|
-
|
|
18563
|
-
|
|
18977
|
+
abortRequested = true;
|
|
18978
|
+
} else {
|
|
18979
|
+
cancelOnAbort = fireCancel;
|
|
18980
|
+
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18564
18981
|
}
|
|
18565
|
-
cancelOnAbort = fireCancel;
|
|
18566
|
-
abortSignal.addEventListener("abort", cancelOnAbort, { once: true });
|
|
18567
18982
|
}
|
|
18983
|
+
let firstChunkFired = false;
|
|
18984
|
+
const onSegment = (_segment) => {
|
|
18985
|
+
if (!firstChunkFired) {
|
|
18986
|
+
firstChunkFired = true;
|
|
18987
|
+
perfSpan?.mark("transport.first_chunk");
|
|
18988
|
+
}
|
|
18989
|
+
};
|
|
18568
18990
|
try {
|
|
18991
|
+
if (abortRequested) {
|
|
18992
|
+
throw new DOMException("Aborted before prompt started", "AbortError");
|
|
18993
|
+
}
|
|
18994
|
+
perfSpan?.mark("transport.prompt_dispatched", {
|
|
18995
|
+
transportKind: this.config?.transport.type ?? inferTransportKind(this.transport)
|
|
18996
|
+
});
|
|
18569
18997
|
return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply, replyContext, {
|
|
18570
18998
|
...media ? { media } : {},
|
|
18999
|
+
...reply ? { onSegment } : {},
|
|
18571
19000
|
...onToolEvent ? { onToolEvent } : {}
|
|
18572
19001
|
}));
|
|
19002
|
+
} catch (error2) {
|
|
19003
|
+
localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
|
|
19004
|
+
throw error2;
|
|
18573
19005
|
} finally {
|
|
19006
|
+
if (abortRequested && localOutcome === "ok") {
|
|
19007
|
+
localOutcome = "aborted";
|
|
19008
|
+
}
|
|
19009
|
+
perfSpan?.mark("transport.prompt_done", { localOutcome });
|
|
18574
19010
|
done = true;
|
|
18575
19011
|
if (cancelOnAbort && abortSignal) {
|
|
18576
19012
|
abortSignal.removeEventListener("abort", cancelOnAbort);
|
|
@@ -18631,6 +19067,12 @@ class CommandRouter {
|
|
|
18631
19067
|
}
|
|
18632
19068
|
}
|
|
18633
19069
|
}
|
|
19070
|
+
function isAbortError2(error2) {
|
|
19071
|
+
return error2 instanceof Error && error2.name === "AbortError";
|
|
19072
|
+
}
|
|
19073
|
+
function inferTransportKind(transport) {
|
|
19074
|
+
return transport.constructor.name.includes("Bridge") ? "acpx-bridge" : "acpx-cli";
|
|
19075
|
+
}
|
|
18634
19076
|
var init_command_router = __esm(() => {
|
|
18635
19077
|
init_app_logger();
|
|
18636
19078
|
init_acpx_session_index();
|
|
@@ -18720,7 +19162,8 @@ class ConsoleAgent {
|
|
|
18720
19162
|
mimeType: m.mimeType,
|
|
18721
19163
|
...m.fileName ? { fileName: m.fileName } : {}
|
|
18722
19164
|
})) : undefined;
|
|
18723
|
-
|
|
19165
|
+
request.perfSpan?.mark("agent.dispatched");
|
|
19166
|
+
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.perfSpan);
|
|
18724
19167
|
}
|
|
18725
19168
|
isKnownCommand(text) {
|
|
18726
19169
|
return isKnownWeacpxCommandText(text);
|
|
@@ -18868,6 +19311,8 @@ class OrchestrationServer {
|
|
|
18868
19311
|
return await this.handlers.listTasks(this.parseTaskListFilter(params));
|
|
18869
19312
|
case "task.wait":
|
|
18870
19313
|
return await this.handlers.waitTask(this.parseWaitTaskInput(params));
|
|
19314
|
+
case "task.watch":
|
|
19315
|
+
return await this.handlers.watchTask(this.parseWatchTaskInput(params));
|
|
18871
19316
|
case "task.approve":
|
|
18872
19317
|
requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
|
|
18873
19318
|
return await this.handlers.approveTask({
|
|
@@ -19054,6 +19499,23 @@ class OrchestrationServer {
|
|
|
19054
19499
|
...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
|
|
19055
19500
|
};
|
|
19056
19501
|
}
|
|
19502
|
+
parseWatchTaskInput(params) {
|
|
19503
|
+
requireOnlyKeys(params, ["coordinatorSession", "taskId", "afterSeq", "mode", "includeProgress", "timeoutMs", "pollIntervalMs"], "params");
|
|
19504
|
+
const afterSeq = requireOptionalIntegerInRange(params, "afterSeq", 0, Number.MAX_SAFE_INTEGER);
|
|
19505
|
+
const mode = requireOptionalEnum(params, "mode", ["next_event", "until_attention_or_terminal"]);
|
|
19506
|
+
const includeProgress = requireOptionalBoolean(params, "includeProgress");
|
|
19507
|
+
const timeoutMs = requireOptionalIntegerInRange(params, "timeoutMs", 0, MAX_TASK_WATCH_TIMEOUT_MS);
|
|
19508
|
+
const pollIntervalMs = requireOptionalIntegerInRange(params, "pollIntervalMs", 1, MAX_TASK_WATCH_POLL_INTERVAL_MS);
|
|
19509
|
+
return {
|
|
19510
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
19511
|
+
taskId: requireString(params, "taskId"),
|
|
19512
|
+
...afterSeq !== undefined ? { afterSeq } : {},
|
|
19513
|
+
...mode !== undefined ? { mode } : {},
|
|
19514
|
+
...includeProgress !== undefined ? { includeProgress } : {},
|
|
19515
|
+
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
19516
|
+
...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
|
|
19517
|
+
};
|
|
19518
|
+
}
|
|
19057
19519
|
parseWorkerRaiseQuestionInput(params) {
|
|
19058
19520
|
requireOnlyKeys(params, ["taskId", "sourceHandle", "question", "whyBlocked", "whatIsNeeded"], "params");
|
|
19059
19521
|
return {
|
|
@@ -19297,6 +19759,7 @@ var init_orchestration_server = __esm(() => {
|
|
|
19297
19759
|
"task.get",
|
|
19298
19760
|
"task.list",
|
|
19299
19761
|
"task.wait",
|
|
19762
|
+
"task.watch",
|
|
19300
19763
|
"task.approve",
|
|
19301
19764
|
"task.reject",
|
|
19302
19765
|
"task.cancel",
|
|
@@ -19316,30 +19779,92 @@ var init_orchestration_server = __esm(() => {
|
|
|
19316
19779
|
|
|
19317
19780
|
// src/orchestration/progress-line-parser.ts
|
|
19318
19781
|
class ProgressLineBuffer {
|
|
19319
|
-
|
|
19782
|
+
pending = "";
|
|
19783
|
+
feed(segment, options = {}) {
|
|
19784
|
+
const hadPending = this.pending.length > 0;
|
|
19785
|
+
this.pending += segment;
|
|
19320
19786
|
const summaries = [];
|
|
19321
|
-
|
|
19322
|
-
`)
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19787
|
+
let newlineIndex = this.pending.indexOf(`
|
|
19788
|
+
`);
|
|
19789
|
+
while (newlineIndex !== -1) {
|
|
19790
|
+
const line = this.pending.slice(0, newlineIndex).replace(/\r$/, "");
|
|
19791
|
+
this.pending = this.pending.slice(newlineIndex + 1);
|
|
19792
|
+
this.extractLine(line, summaries);
|
|
19793
|
+
newlineIndex = this.pending.indexOf(`
|
|
19794
|
+
`);
|
|
19795
|
+
}
|
|
19796
|
+
if (options.segmentComplete === true && !hadPending && this.pending.startsWith(PROGRESS_PREFIX)) {
|
|
19797
|
+
this.extractLine(this.pending.replace(/\r$/, ""), summaries);
|
|
19798
|
+
this.pending = "";
|
|
19799
|
+
return summaries;
|
|
19800
|
+
}
|
|
19801
|
+
this.trimPendingIfHopeless();
|
|
19802
|
+
return summaries;
|
|
19803
|
+
}
|
|
19804
|
+
flush() {
|
|
19805
|
+
const summaries = [];
|
|
19806
|
+
if (this.pending.length > 0) {
|
|
19807
|
+
this.extractLine(this.pending.replace(/\r$/, ""), summaries);
|
|
19808
|
+
this.pending = "";
|
|
19329
19809
|
}
|
|
19330
19810
|
return summaries;
|
|
19331
19811
|
}
|
|
19812
|
+
extractLine(line, summaries) {
|
|
19813
|
+
if (line.startsWith(PROGRESS_PREFIX)) {
|
|
19814
|
+
const summary = sanitizeProgressSummary(line.slice(PROGRESS_PREFIX.length));
|
|
19815
|
+
if (summary.length > 0) {
|
|
19816
|
+
summaries.push(summary);
|
|
19817
|
+
}
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19820
|
+
trimPendingIfHopeless() {
|
|
19821
|
+
if (this.pending.length === 0)
|
|
19822
|
+
return;
|
|
19823
|
+
if (PROGRESS_PREFIX.startsWith(this.pending) || this.pending.startsWith(PROGRESS_PREFIX)) {
|
|
19824
|
+
if (this.pending.length > MAX_PENDING_LINE_LENGTH) {
|
|
19825
|
+
this.pending = "";
|
|
19826
|
+
}
|
|
19827
|
+
return;
|
|
19828
|
+
}
|
|
19829
|
+
this.pending = "";
|
|
19830
|
+
}
|
|
19831
|
+
}
|
|
19832
|
+
function sanitizeProgressSummary(summary) {
|
|
19833
|
+
const cleaned = summary.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").trim();
|
|
19834
|
+
if (cleaned.length <= MAX_PROGRESS_SUMMARY_LENGTH) {
|
|
19835
|
+
return cleaned;
|
|
19836
|
+
}
|
|
19837
|
+
return `${cleaned.slice(0, MAX_PROGRESS_SUMMARY_LENGTH - 3)}...`;
|
|
19332
19838
|
}
|
|
19333
19839
|
function stripProgressLines(text) {
|
|
19334
|
-
return text.split(
|
|
19335
|
-
|
|
19840
|
+
return text.split(/\r\n|\n|\r/).filter((line) => {
|
|
19841
|
+
const normalized = normalizeProgressLinePrefix(line);
|
|
19842
|
+
return !normalized.startsWith(PROGRESS_PREFIX) && !(line.length > 0 && normalized.length === 0);
|
|
19843
|
+
}).join(`
|
|
19336
19844
|
`).trim();
|
|
19337
19845
|
}
|
|
19338
|
-
|
|
19846
|
+
function normalizeProgressLinePrefix(line) {
|
|
19847
|
+
return line.replace(/^(?:\r+|\u001B\[[0-?]*[ -/]*[@-~])+/, "");
|
|
19848
|
+
}
|
|
19849
|
+
var PROGRESS_PREFIX = "[PROGRESS]", MAX_PROGRESS_SUMMARY_LENGTH = 500, MAX_PENDING_LINE_LENGTH = 4096;
|
|
19339
19850
|
|
|
19340
19851
|
// src/orchestration/orchestration-service.ts
|
|
19341
19852
|
import { createHash as createHash2 } from "node:crypto";
|
|
19342
19853
|
import { basename as basename2, isAbsolute as isAbsolute2, normalize } from "node:path";
|
|
19854
|
+
function clampWatchTimeout(value) {
|
|
19855
|
+
if (value === undefined)
|
|
19856
|
+
return DEFAULT_TASK_WATCH_TIMEOUT_MS;
|
|
19857
|
+
if (!Number.isFinite(value) || value < 0)
|
|
19858
|
+
return 0;
|
|
19859
|
+
return Math.min(Math.floor(value), MAX_TASK_WATCH_TIMEOUT_MS);
|
|
19860
|
+
}
|
|
19861
|
+
function clampWatchPollInterval(value) {
|
|
19862
|
+
if (value === undefined)
|
|
19863
|
+
return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
|
|
19864
|
+
if (!Number.isFinite(value) || value < 1)
|
|
19865
|
+
return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
|
|
19866
|
+
return Math.min(value, MAX_TASK_WATCH_POLL_INTERVAL_MS);
|
|
19867
|
+
}
|
|
19343
19868
|
|
|
19344
19869
|
class OrchestrationService {
|
|
19345
19870
|
deps;
|
|
@@ -19556,6 +20081,8 @@ class OrchestrationService {
|
|
|
19556
20081
|
resultText: "",
|
|
19557
20082
|
createdAt: now,
|
|
19558
20083
|
updatedAt: now,
|
|
20084
|
+
eventSeq: 1,
|
|
20085
|
+
events: [{ seq: 1, at: now, type: "created", status: "running", message: "Task created" }],
|
|
19559
20086
|
...input.chatKey ? { chatKey: input.chatKey } : {},
|
|
19560
20087
|
...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
|
|
19561
20088
|
...input.accountId ? { accountId: input.accountId } : {}
|
|
@@ -19679,7 +20206,9 @@ class OrchestrationService {
|
|
|
19679
20206
|
summary: "",
|
|
19680
20207
|
resultText: "",
|
|
19681
20208
|
createdAt: now,
|
|
19682
|
-
updatedAt: now
|
|
20209
|
+
updatedAt: now,
|
|
20210
|
+
eventSeq: 1,
|
|
20211
|
+
events: [{ seq: 1, at: now, type: "created", status, message: "Task created" }]
|
|
19683
20212
|
};
|
|
19684
20213
|
if (preflight.normalizedGroupId) {
|
|
19685
20214
|
const group = this.ensureGroups(state)[preflight.normalizedGroupId];
|
|
@@ -19850,6 +20379,11 @@ class OrchestrationService {
|
|
|
19850
20379
|
current.summary = message;
|
|
19851
20380
|
current.resultText = "";
|
|
19852
20381
|
current.updatedAt = now;
|
|
20382
|
+
this.appendTaskEvent(current, now, "status_changed", {
|
|
20383
|
+
status: "failed",
|
|
20384
|
+
summary: message,
|
|
20385
|
+
message: "Task failed during startup"
|
|
20386
|
+
});
|
|
19853
20387
|
restoreOrDeleteBinding();
|
|
19854
20388
|
await this.deps.saveState(state);
|
|
19855
20389
|
return true;
|
|
@@ -19942,6 +20476,11 @@ class OrchestrationService {
|
|
|
19942
20476
|
task2.status = input.status ?? "completed";
|
|
19943
20477
|
task2.summary = input.summary ?? "";
|
|
19944
20478
|
task2.resultText = stripProgressLines(input.resultText ?? "");
|
|
20479
|
+
this.appendTaskEvent(task2, updatedAt, "status_changed", {
|
|
20480
|
+
status: task2.status,
|
|
20481
|
+
summary: task2.summary,
|
|
20482
|
+
message: task2.status === "completed" ? "Task completed" : task2.status === "failed" ? "Task failed" : "Task cancelled"
|
|
20483
|
+
});
|
|
19945
20484
|
if (task2.status === "completed" || task2.status === "failed") {
|
|
19946
20485
|
if (!this.isExternalCoordinatorSession(state, task2.coordinatorSession)) {
|
|
19947
20486
|
task2.injectionPending = true;
|
|
@@ -19970,6 +20509,10 @@ class OrchestrationService {
|
|
|
19970
20509
|
resultId: this.deps.createId(),
|
|
19971
20510
|
resultText: task2.resultText
|
|
19972
20511
|
};
|
|
20512
|
+
this.appendTaskEvent(task2, updatedAt, "attention_required", {
|
|
20513
|
+
status: task2.status,
|
|
20514
|
+
message: "Task result requires contested review"
|
|
20515
|
+
});
|
|
19973
20516
|
task2.correctionPending = undefined;
|
|
19974
20517
|
task2.cancelRequestedAt = undefined;
|
|
19975
20518
|
task2.cancelCompletedAt = undefined;
|
|
@@ -20078,6 +20621,64 @@ class OrchestrationService {
|
|
|
20078
20621
|
await sleep2(Math.min(pollIntervalMs, remainingMs));
|
|
20079
20622
|
}
|
|
20080
20623
|
}
|
|
20624
|
+
async watchTask(input) {
|
|
20625
|
+
const timeoutMs = clampWatchTimeout(input.timeoutMs);
|
|
20626
|
+
const pollIntervalMs = clampWatchPollInterval(input.pollIntervalMs);
|
|
20627
|
+
const afterSeq = Math.max(0, Math.floor(input.afterSeq ?? 0));
|
|
20628
|
+
const mode = input.mode ?? "until_attention_or_terminal";
|
|
20629
|
+
const includeProgress = input.includeProgress ?? true;
|
|
20630
|
+
const deadline = Date.now() + timeoutMs;
|
|
20631
|
+
while (true) {
|
|
20632
|
+
const state = await this.deps.loadState();
|
|
20633
|
+
const task = state.orchestration.tasks[input.taskId];
|
|
20634
|
+
if (!task || task.coordinatorSession !== input.coordinatorSession) {
|
|
20635
|
+
return { status: "not_found", task: null, events: [], nextAfterSeq: afterSeq };
|
|
20636
|
+
}
|
|
20637
|
+
const snapshot = { ...task };
|
|
20638
|
+
const allEvents = task.events ?? [];
|
|
20639
|
+
const filteredEvents = allEvents.filter((event) => event.seq > afterSeq).filter((event) => includeProgress || event.type !== "progress");
|
|
20640
|
+
const nextAfterSeq = task.eventSeq ?? allEvents.at(-1)?.seq ?? afterSeq;
|
|
20641
|
+
const historyTruncated = allEvents.length > 0 && afterSeq < allEvents[0].seq - 1;
|
|
20642
|
+
if (isTerminalTaskStatus2(task.status) && task.reviewPending === undefined) {
|
|
20643
|
+
return {
|
|
20644
|
+
status: "terminal",
|
|
20645
|
+
task: snapshot,
|
|
20646
|
+
events: filteredEvents.map((event) => ({ ...event })),
|
|
20647
|
+
nextAfterSeq,
|
|
20648
|
+
...historyTruncated ? { historyTruncated } : {}
|
|
20649
|
+
};
|
|
20650
|
+
}
|
|
20651
|
+
if (isAttentionRequiredTask(task)) {
|
|
20652
|
+
return {
|
|
20653
|
+
status: "attention_required",
|
|
20654
|
+
task: snapshot,
|
|
20655
|
+
events: filteredEvents.map((event) => ({ ...event })),
|
|
20656
|
+
nextAfterSeq,
|
|
20657
|
+
...historyTruncated ? { historyTruncated } : {}
|
|
20658
|
+
};
|
|
20659
|
+
}
|
|
20660
|
+
if (filteredEvents.length > 0 && mode === "next_event") {
|
|
20661
|
+
return {
|
|
20662
|
+
status: "event",
|
|
20663
|
+
task: snapshot,
|
|
20664
|
+
events: filteredEvents.map((event) => ({ ...event })),
|
|
20665
|
+
nextAfterSeq,
|
|
20666
|
+
...historyTruncated ? { historyTruncated } : {}
|
|
20667
|
+
};
|
|
20668
|
+
}
|
|
20669
|
+
const remainingMs = deadline - Date.now();
|
|
20670
|
+
if (remainingMs <= 0) {
|
|
20671
|
+
return {
|
|
20672
|
+
status: "timeout",
|
|
20673
|
+
task: snapshot,
|
|
20674
|
+
events: filteredEvents.map((event) => ({ ...event })),
|
|
20675
|
+
nextAfterSeq,
|
|
20676
|
+
...historyTruncated ? { historyTruncated } : {}
|
|
20677
|
+
};
|
|
20678
|
+
}
|
|
20679
|
+
await sleep2(Math.min(pollIntervalMs, remainingMs));
|
|
20680
|
+
}
|
|
20681
|
+
}
|
|
20081
20682
|
async recordCoordinatorRouteContext(input) {
|
|
20082
20683
|
if (input.coordinatorSession.trim().length === 0) {
|
|
20083
20684
|
throw new Error("coordinatorSession must be a non-empty string");
|
|
@@ -20157,6 +20758,10 @@ class OrchestrationService {
|
|
|
20157
20758
|
status: "open"
|
|
20158
20759
|
};
|
|
20159
20760
|
task.updatedAt = now;
|
|
20761
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
20762
|
+
status: "blocked",
|
|
20763
|
+
message: input.question.trim()
|
|
20764
|
+
});
|
|
20160
20765
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20161
20766
|
await this.deps.saveState(state);
|
|
20162
20767
|
return {
|
|
@@ -20214,6 +20819,10 @@ class OrchestrationService {
|
|
|
20214
20819
|
lastResumeError: undefined
|
|
20215
20820
|
};
|
|
20216
20821
|
task.updatedAt = now;
|
|
20822
|
+
this.appendTaskEvent(task, now, "status_changed", {
|
|
20823
|
+
status: "running",
|
|
20824
|
+
message: "Blocker question answered"
|
|
20825
|
+
});
|
|
20217
20826
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20218
20827
|
await this.deps.saveState(state);
|
|
20219
20828
|
return {
|
|
@@ -20268,6 +20877,10 @@ class OrchestrationService {
|
|
|
20268
20877
|
};
|
|
20269
20878
|
task.cancelRequestedAt = task.cancelRequestedAt ?? now;
|
|
20270
20879
|
task.updatedAt = now;
|
|
20880
|
+
this.appendTaskEvent(task, now, "cancel_requested", {
|
|
20881
|
+
status: task.status,
|
|
20882
|
+
message: "Correction requested for misrouted answer"
|
|
20883
|
+
});
|
|
20271
20884
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20272
20885
|
await this.deps.saveState(state);
|
|
20273
20886
|
return {
|
|
@@ -20286,6 +20899,10 @@ class OrchestrationService {
|
|
|
20286
20899
|
task.noticePending = false;
|
|
20287
20900
|
task.lastNoticeError = undefined;
|
|
20288
20901
|
task.updatedAt = now;
|
|
20902
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
20903
|
+
status: task.status,
|
|
20904
|
+
message: "Task result requires contested review"
|
|
20905
|
+
});
|
|
20289
20906
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20290
20907
|
await this.deps.saveState(state);
|
|
20291
20908
|
return {
|
|
@@ -20392,6 +21009,10 @@ class OrchestrationService {
|
|
|
20392
21009
|
packageId
|
|
20393
21010
|
};
|
|
20394
21011
|
task.updatedAt = now;
|
|
21012
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
21013
|
+
status: "waiting_for_human",
|
|
21014
|
+
message: task.openQuestion.question
|
|
21015
|
+
});
|
|
20395
21016
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20396
21017
|
}
|
|
20397
21018
|
this.ensureHumanQuestionPackages(state)[packageId] = packageRecord;
|
|
@@ -20479,6 +21100,10 @@ class OrchestrationService {
|
|
|
20479
21100
|
packageId: input.packageId
|
|
20480
21101
|
};
|
|
20481
21102
|
task.updatedAt = now;
|
|
21103
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
21104
|
+
status: "waiting_for_human",
|
|
21105
|
+
message: task.openQuestion.question
|
|
21106
|
+
});
|
|
20482
21107
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20483
21108
|
}
|
|
20484
21109
|
await this.deps.saveState(state);
|
|
@@ -20662,11 +21287,21 @@ class OrchestrationService {
|
|
|
20662
21287
|
task.summary = "";
|
|
20663
21288
|
task.resultText = "";
|
|
20664
21289
|
task.openQuestion = this.buildReplacementOpenQuestion(task, replacementQuestionId, now, packageId);
|
|
21290
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
21291
|
+
status: task.status,
|
|
21292
|
+
message: task.openQuestion.question
|
|
21293
|
+
});
|
|
20665
21294
|
} else if ((task.status === "completed" || task.status === "failed") && task.chatKey && task.replyContextToken && task.noticeSentAt === undefined) {
|
|
20666
21295
|
task.noticePending = true;
|
|
20667
21296
|
task.lastNoticeError = undefined;
|
|
20668
21297
|
}
|
|
20669
21298
|
task.updatedAt = now;
|
|
21299
|
+
if (input.decision === "accept") {
|
|
21300
|
+
this.appendTaskEvent(task, now, "status_changed", {
|
|
21301
|
+
status: task.status,
|
|
21302
|
+
message: "Contested result accepted"
|
|
21303
|
+
});
|
|
21304
|
+
}
|
|
20670
21305
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
20671
21306
|
await this.deps.saveState(state);
|
|
20672
21307
|
return {
|
|
@@ -21044,7 +21679,7 @@ class OrchestrationService {
|
|
|
21044
21679
|
});
|
|
21045
21680
|
}
|
|
21046
21681
|
}
|
|
21047
|
-
async recordTaskProgress(taskId) {
|
|
21682
|
+
async recordTaskProgress(taskId, summary) {
|
|
21048
21683
|
return await this.mutate(async () => {
|
|
21049
21684
|
const state = await this.deps.loadState();
|
|
21050
21685
|
const task = state.orchestration.tasks[taskId];
|
|
@@ -21052,6 +21687,21 @@ class OrchestrationService {
|
|
|
21052
21687
|
throw new Error(`task "${taskId}" does not exist`);
|
|
21053
21688
|
}
|
|
21054
21689
|
task.lastProgressAt = this.deps.now().toISOString();
|
|
21690
|
+
if (summary !== undefined) {
|
|
21691
|
+
const cleaned = sanitizeProgressSummary(summary);
|
|
21692
|
+
if (cleaned.length > 0) {
|
|
21693
|
+
task.lastProgressSummary = cleaned;
|
|
21694
|
+
this.appendTaskEvent(task, task.lastProgressAt, "progress", {
|
|
21695
|
+
status: task.status,
|
|
21696
|
+
summary: cleaned
|
|
21697
|
+
});
|
|
21698
|
+
}
|
|
21699
|
+
} else {
|
|
21700
|
+
this.appendTaskEvent(task, task.lastProgressAt, "progress", {
|
|
21701
|
+
status: task.status,
|
|
21702
|
+
message: "heartbeat"
|
|
21703
|
+
});
|
|
21704
|
+
}
|
|
21055
21705
|
task.updatedAt = task.lastProgressAt;
|
|
21056
21706
|
await this.deps.saveState(state);
|
|
21057
21707
|
return { ...task };
|
|
@@ -21099,6 +21749,12 @@ class OrchestrationService {
|
|
|
21099
21749
|
const shouldPropagate = task.cancelRequestedAt === undefined;
|
|
21100
21750
|
task.cancelRequestedAt = task.cancelRequestedAt ?? now;
|
|
21101
21751
|
task.updatedAt = now;
|
|
21752
|
+
if (shouldPropagate) {
|
|
21753
|
+
this.appendTaskEvent(task, now, "cancel_requested", {
|
|
21754
|
+
status: task.status,
|
|
21755
|
+
message: "Cancellation requested"
|
|
21756
|
+
});
|
|
21757
|
+
}
|
|
21102
21758
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
21103
21759
|
await this.deps.saveState(state);
|
|
21104
21760
|
return { task: { ...task }, shouldPropagate, closedPackageId: undefined };
|
|
@@ -21110,6 +21766,10 @@ class OrchestrationService {
|
|
|
21110
21766
|
task.cancelCompletedAt = now;
|
|
21111
21767
|
task.lastCancelError = undefined;
|
|
21112
21768
|
task.updatedAt = now;
|
|
21769
|
+
this.appendTaskEvent(task, now, "status_changed", {
|
|
21770
|
+
status: "cancelled",
|
|
21771
|
+
message: "Task cancelled"
|
|
21772
|
+
});
|
|
21113
21773
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
21114
21774
|
await this.deps.saveState(state);
|
|
21115
21775
|
return { task: { ...task }, shouldPropagate: false, closedPackageId };
|
|
@@ -21144,10 +21804,18 @@ class OrchestrationService {
|
|
|
21144
21804
|
task.cancelRequestedAt = undefined;
|
|
21145
21805
|
task.cancelCompletedAt = undefined;
|
|
21146
21806
|
task.lastCancelError = undefined;
|
|
21807
|
+
this.appendTaskEvent(task, now, "attention_required", {
|
|
21808
|
+
status: task.status,
|
|
21809
|
+
message: task.openQuestion.question
|
|
21810
|
+
});
|
|
21147
21811
|
} else {
|
|
21148
21812
|
task.status = "cancelled";
|
|
21149
21813
|
task.cancelCompletedAt = now;
|
|
21150
21814
|
task.lastCancelError = undefined;
|
|
21815
|
+
this.appendTaskEvent(task, now, "status_changed", {
|
|
21816
|
+
status: "cancelled",
|
|
21817
|
+
message: "Task cancelled"
|
|
21818
|
+
});
|
|
21151
21819
|
}
|
|
21152
21820
|
task.updatedAt = now;
|
|
21153
21821
|
this.bumpGroupUpdated(state, task.groupId, now);
|
|
@@ -21189,6 +21857,10 @@ class OrchestrationService {
|
|
|
21189
21857
|
}
|
|
21190
21858
|
task2.lastCancelError = errorMessage;
|
|
21191
21859
|
task2.updatedAt = this.deps.now().toISOString();
|
|
21860
|
+
this.appendTaskEvent(task2, task2.updatedAt, "progress", {
|
|
21861
|
+
status: task2.status,
|
|
21862
|
+
message: `Cancellation failed: ${errorMessage}`
|
|
21863
|
+
});
|
|
21192
21864
|
await this.deps.saveState(state);
|
|
21193
21865
|
return { ...task2 };
|
|
21194
21866
|
});
|
|
@@ -21246,6 +21918,10 @@ class OrchestrationService {
|
|
|
21246
21918
|
task.workerSession = ensuredWorkerSession;
|
|
21247
21919
|
task.status = "running";
|
|
21248
21920
|
task.updatedAt = this.deps.now().toISOString();
|
|
21921
|
+
this.appendTaskEvent(task, task.updatedAt, "status_changed", {
|
|
21922
|
+
status: "running",
|
|
21923
|
+
message: "Task approved"
|
|
21924
|
+
});
|
|
21249
21925
|
state.orchestration.workerBindings[ensuredWorkerSession] = {
|
|
21250
21926
|
sourceHandle: ensuredWorkerSession,
|
|
21251
21927
|
coordinatorSession: task.coordinatorSession,
|
|
@@ -21316,6 +21992,11 @@ class OrchestrationService {
|
|
|
21316
21992
|
task2.status = "cancelled";
|
|
21317
21993
|
task2.summary = "rejected";
|
|
21318
21994
|
task2.updatedAt = this.deps.now().toISOString();
|
|
21995
|
+
this.appendTaskEvent(task2, task2.updatedAt, "status_changed", {
|
|
21996
|
+
status: "cancelled",
|
|
21997
|
+
summary: "rejected",
|
|
21998
|
+
message: "Task rejected"
|
|
21999
|
+
});
|
|
21319
22000
|
await this.deps.saveState(state);
|
|
21320
22001
|
return { ...task2 };
|
|
21321
22002
|
});
|
|
@@ -22244,6 +22925,20 @@ class OrchestrationService {
|
|
|
22244
22925
|
}
|
|
22245
22926
|
})();
|
|
22246
22927
|
}
|
|
22928
|
+
appendTaskEvent(task, at, type, details = {}) {
|
|
22929
|
+
const nextSeq = (task.eventSeq ?? 0) + 1;
|
|
22930
|
+
task.eventSeq = nextSeq;
|
|
22931
|
+
const events = task.events ?? [];
|
|
22932
|
+
events.push({
|
|
22933
|
+
seq: nextSeq,
|
|
22934
|
+
at,
|
|
22935
|
+
type,
|
|
22936
|
+
...details.status ? { status: details.status } : {},
|
|
22937
|
+
...details.summary ? { summary: details.summary } : {},
|
|
22938
|
+
...details.message ? { message: details.message } : {}
|
|
22939
|
+
});
|
|
22940
|
+
task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
|
|
22941
|
+
}
|
|
22247
22942
|
}
|
|
22248
22943
|
function isTerminalTaskStatus2(status) {
|
|
22249
22944
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
@@ -22275,6 +22970,7 @@ async function sleep2(ms) {
|
|
|
22275
22970
|
function isRequestDelegateInput(input) {
|
|
22276
22971
|
return "sourceKind" in input;
|
|
22277
22972
|
}
|
|
22973
|
+
var MAX_TASK_EVENTS_PER_TASK = 200;
|
|
22278
22974
|
var init_orchestration_service = __esm(() => {
|
|
22279
22975
|
init_quota_errors();
|
|
22280
22976
|
init_task_wait_timeouts();
|
|
@@ -22738,12 +23434,21 @@ async function runConsole(paths, deps) {
|
|
|
22738
23434
|
runtimeForGc.orchestration.service.purgeExpiredResetCoordinators({ cutoffDays: 7, trigger: "interval" }).catch(() => {});
|
|
22739
23435
|
}, 86400000);
|
|
22740
23436
|
}
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
23437
|
+
try {
|
|
23438
|
+
await deps.channels.startAll({
|
|
23439
|
+
agent: runtime.agent,
|
|
23440
|
+
abortSignal: shutdownController.signal,
|
|
23441
|
+
quota: runtime.quota,
|
|
23442
|
+
logger: runtime.logger,
|
|
23443
|
+
perfTracer: runtime.perfTracer
|
|
23444
|
+
});
|
|
23445
|
+
} catch (error2) {
|
|
23446
|
+
if (deps.channelStartupPolicy !== "best-effort") {
|
|
23447
|
+
throw error2;
|
|
23448
|
+
}
|
|
23449
|
+
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) });
|
|
23450
|
+
await waitForShutdown(shutdownController.signal);
|
|
23451
|
+
}
|
|
22747
23452
|
} finally {
|
|
22748
23453
|
await runCleanupSequence({
|
|
22749
23454
|
removeProcessListener,
|
|
@@ -22761,6 +23466,14 @@ async function runConsole(paths, deps) {
|
|
|
22761
23466
|
});
|
|
22762
23467
|
}
|
|
22763
23468
|
}
|
|
23469
|
+
async function waitForShutdown(signal) {
|
|
23470
|
+
if (signal.aborted) {
|
|
23471
|
+
return;
|
|
23472
|
+
}
|
|
23473
|
+
await new Promise((resolve3) => {
|
|
23474
|
+
signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
23475
|
+
});
|
|
23476
|
+
}
|
|
22764
23477
|
async function runCleanupSequence(input) {
|
|
22765
23478
|
let cleanupError = null;
|
|
22766
23479
|
input.removeProcessListener("SIGINT", input.signalHandler);
|
|
@@ -23782,12 +24495,12 @@ var init_streaming_prompt = __esm(() => {
|
|
|
23782
24495
|
|
|
23783
24496
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
23784
24497
|
import { chmod as chmodFs } from "node:fs/promises";
|
|
23785
|
-
import { dirname as
|
|
24498
|
+
import { dirname as dirname12, join as join12 } from "node:path";
|
|
23786
24499
|
function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
|
|
23787
24500
|
if (platform === "win32") {
|
|
23788
24501
|
return null;
|
|
23789
24502
|
}
|
|
23790
|
-
return join12(
|
|
24503
|
+
return join12(dirname12(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
|
|
23791
24504
|
}
|
|
23792
24505
|
async function ensureNodePtyHelperExecutable(helperPath, chmod2 = chmodFs) {
|
|
23793
24506
|
if (!helperPath) {
|
|
@@ -24622,7 +25335,7 @@ __export(exports_main, {
|
|
|
24622
25335
|
});
|
|
24623
25336
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24624
25337
|
import { homedir as homedir9 } from "node:os";
|
|
24625
|
-
import { dirname as
|
|
25338
|
+
import { dirname as dirname13, join as join14 } from "node:path";
|
|
24626
25339
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
24627
25340
|
function startProgressHeartbeat(orchestration, config2, logger2, channel) {
|
|
24628
25341
|
const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
|
|
@@ -24660,6 +25373,11 @@ async function buildApp(paths, deps = {}) {
|
|
|
24660
25373
|
const config2 = await loadConfig(paths.configPath, {
|
|
24661
25374
|
defaultLoggingLevel: deps.defaultLoggingLevel
|
|
24662
25375
|
});
|
|
25376
|
+
const reloadRuntimeConfig = async () => {
|
|
25377
|
+
const updated = await configStore.load();
|
|
25378
|
+
replaceRuntimeConfig(config2, updated);
|
|
25379
|
+
return config2;
|
|
25380
|
+
};
|
|
24663
25381
|
const logger2 = createAppLogger({
|
|
24664
25382
|
filePath: resolveAppLogPath(paths.configPath),
|
|
24665
25383
|
level: config2.logging.level,
|
|
@@ -24669,6 +25387,15 @@ async function buildApp(paths, deps = {}) {
|
|
|
24669
25387
|
now: deps.loggerNow
|
|
24670
25388
|
});
|
|
24671
25389
|
await logger2.cleanup();
|
|
25390
|
+
const perfLogPath = paths.perfLogPath ?? resolvePerfLogPath(paths.configPath);
|
|
25391
|
+
const perfTracer = config2.logging.perf.enabled ? createPerfTracer({
|
|
25392
|
+
filePath: perfLogPath,
|
|
25393
|
+
maxSizeBytes: config2.logging.perf.maxSizeBytes,
|
|
25394
|
+
maxFiles: config2.logging.perf.maxFiles,
|
|
25395
|
+
retentionDays: config2.logging.perf.retentionDays,
|
|
25396
|
+
appLogger: logger2
|
|
25397
|
+
}) : createNoopPerfTracer();
|
|
25398
|
+
await perfTracer.cleanup();
|
|
24672
25399
|
const acpxCommand = resolveAcpxCommand({ configuredCommand: config2.transport.command });
|
|
24673
25400
|
const stateStore = new StateStore(paths.statePath);
|
|
24674
25401
|
const state = await stateStore.load();
|
|
@@ -24849,32 +25576,39 @@ async function buildApp(paths, deps = {}) {
|
|
|
24849
25576
|
};
|
|
24850
25577
|
};
|
|
24851
25578
|
const launchWorkerTurn = (input) => {
|
|
24852
|
-
const session = resolveWorkerRuntimeSession(input);
|
|
24853
|
-
session.mcpCoordinatorSession = input.coordinatorSession;
|
|
24854
|
-
session.mcpSourceHandle = input.workerSession;
|
|
24855
25579
|
const workerDispatch = (async () => {
|
|
24856
25580
|
let taskRecord;
|
|
24857
25581
|
try {
|
|
25582
|
+
await reloadRuntimeConfig();
|
|
25583
|
+
const session = resolveWorkerRuntimeSession(input);
|
|
25584
|
+
session.mcpCoordinatorSession = input.coordinatorSession;
|
|
25585
|
+
session.mcpSourceHandle = input.workerSession;
|
|
24858
25586
|
const progressBuffer = new ProgressLineBuffer;
|
|
25587
|
+
const recordProgress = async (summary) => {
|
|
25588
|
+
try {
|
|
25589
|
+
await orchestration.recordTaskProgress(input.taskId, summary);
|
|
25590
|
+
const taskState = await orchestration.getTask(input.taskId);
|
|
25591
|
+
if (taskState?.chatKey && taskState.replyContextToken && deps.channel) {
|
|
25592
|
+
await deps.channel.notifyTaskProgress(taskState, renderTaskProgress(taskState, summary));
|
|
25593
|
+
}
|
|
25594
|
+
} catch (error2) {
|
|
25595
|
+
await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
|
|
25596
|
+
taskId: input.taskId,
|
|
25597
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
25598
|
+
});
|
|
25599
|
+
}
|
|
25600
|
+
};
|
|
24859
25601
|
const result = await transport.prompt(session, input.promptText, undefined, undefined, {
|
|
24860
25602
|
onSegment: async (chunk) => {
|
|
24861
|
-
const summaries = progressBuffer.feed(chunk);
|
|
25603
|
+
const summaries = progressBuffer.feed(chunk, { segmentComplete: true });
|
|
24862
25604
|
for (const summary of summaries) {
|
|
24863
|
-
|
|
24864
|
-
await orchestration.recordTaskProgress(input.taskId);
|
|
24865
|
-
const taskState = await orchestration.getTask(input.taskId);
|
|
24866
|
-
if (taskState?.chatKey && taskState.replyContextToken && deps.channel) {
|
|
24867
|
-
await deps.channel.notifyTaskProgress(taskState, renderTaskProgress(taskState, summary));
|
|
24868
|
-
}
|
|
24869
|
-
} catch (error2) {
|
|
24870
|
-
await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
|
|
24871
|
-
taskId: input.taskId,
|
|
24872
|
-
message: error2 instanceof Error ? error2.message : String(error2)
|
|
24873
|
-
});
|
|
24874
|
-
}
|
|
25605
|
+
await recordProgress(summary);
|
|
24875
25606
|
}
|
|
24876
25607
|
}
|
|
24877
25608
|
});
|
|
25609
|
+
for (const summary of progressBuffer.flush()) {
|
|
25610
|
+
await recordProgress(summary);
|
|
25611
|
+
}
|
|
24878
25612
|
taskRecord = await finalizeWorkerTurn({
|
|
24879
25613
|
taskId: input.taskId,
|
|
24880
25614
|
workerSession: input.workerSession,
|
|
@@ -24938,6 +25672,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
24938
25672
|
},
|
|
24939
25673
|
stateMutex,
|
|
24940
25674
|
ensureWorkerSession: async ({ workerSession, targetAgent, workspace, cwd, coordinatorSession }) => {
|
|
25675
|
+
await reloadRuntimeConfig();
|
|
24941
25676
|
const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
|
|
24942
25677
|
session.mcpCoordinatorSession = coordinatorSession;
|
|
24943
25678
|
session.mcpSourceHandle = workerSession;
|
|
@@ -25027,6 +25762,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
25027
25762
|
stateStore,
|
|
25028
25763
|
configStore,
|
|
25029
25764
|
logger: logger2,
|
|
25765
|
+
perfTracer,
|
|
25030
25766
|
quota,
|
|
25031
25767
|
transport,
|
|
25032
25768
|
orchestration: {
|
|
@@ -25043,6 +25779,13 @@ async function buildApp(paths, deps = {}) {
|
|
|
25043
25779
|
if ("dispose" in transport && typeof transport.dispose === "function") {
|
|
25044
25780
|
await transport.dispose();
|
|
25045
25781
|
}
|
|
25782
|
+
try {
|
|
25783
|
+
await perfTracer.flush();
|
|
25784
|
+
} catch (err) {
|
|
25785
|
+
await logger2.error("perf.flush_failed", "perf tracer flush failed during shutdown", {
|
|
25786
|
+
error: err instanceof Error ? err.message : String(err)
|
|
25787
|
+
}).catch(() => {});
|
|
25788
|
+
}
|
|
25046
25789
|
await logger2.flush();
|
|
25047
25790
|
}
|
|
25048
25791
|
};
|
|
@@ -25052,6 +25795,9 @@ function replaceRuntimeState(target, source) {
|
|
|
25052
25795
|
target.chat_contexts = source.chat_contexts;
|
|
25053
25796
|
target.orchestration = source.orchestration;
|
|
25054
25797
|
}
|
|
25798
|
+
function replaceRuntimeConfig(target, source) {
|
|
25799
|
+
Object.assign(target, source);
|
|
25800
|
+
}
|
|
25055
25801
|
async function main() {
|
|
25056
25802
|
const paths = resolveRuntimePaths();
|
|
25057
25803
|
try {
|
|
@@ -25078,7 +25824,7 @@ async function main() {
|
|
|
25078
25824
|
}
|
|
25079
25825
|
}
|
|
25080
25826
|
async function prepareChannelMedia(configPath, config2) {
|
|
25081
|
-
const runtimeDir = join14(
|
|
25827
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25082
25828
|
const mediaRootDir = join14(runtimeDir, "media");
|
|
25083
25829
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
25084
25830
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -25093,10 +25839,11 @@ function resolveRuntimePaths() {
|
|
|
25093
25839
|
throw new Error("Unable to resolve the current user home directory");
|
|
25094
25840
|
}
|
|
25095
25841
|
const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
|
|
25096
|
-
const runtimeDir = join14(
|
|
25842
|
+
const runtimeDir = join14(dirname13(configPath), "runtime");
|
|
25097
25843
|
return {
|
|
25098
25844
|
configPath,
|
|
25099
25845
|
statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
|
|
25846
|
+
perfLogPath: join14(runtimeDir, "perf.log"),
|
|
25100
25847
|
orchestrationSocketPath: process.env.WEACPX_ORCHESTRATION_SOCKET ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
|
|
25101
25848
|
};
|
|
25102
25849
|
}
|
|
@@ -25107,10 +25854,15 @@ function resolveBridgeEntryPath() {
|
|
|
25107
25854
|
return fileURLToPath4(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
25108
25855
|
}
|
|
25109
25856
|
function resolveAppLogPath(configPath) {
|
|
25110
|
-
const rootDir =
|
|
25857
|
+
const rootDir = dirname13(configPath);
|
|
25111
25858
|
const runtimeDir = join14(rootDir, "runtime");
|
|
25112
25859
|
return join14(runtimeDir, "app.log");
|
|
25113
25860
|
}
|
|
25861
|
+
function resolvePerfLogPath(configPath) {
|
|
25862
|
+
const rootDir = dirname13(configPath);
|
|
25863
|
+
const runtimeDir = join14(rootDir, "runtime");
|
|
25864
|
+
return join14(runtimeDir, "perf.log");
|
|
25865
|
+
}
|
|
25114
25866
|
function resolveOrchestrationSocketPathFromConfigPath(configPath) {
|
|
25115
25867
|
const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
|
|
25116
25868
|
return resolveDaemonOrchestrationSocketPath(runtimeDir);
|
|
@@ -25141,6 +25893,7 @@ var init_main = __esm(async () => {
|
|
|
25141
25893
|
init_media_store();
|
|
25142
25894
|
init_quota_errors();
|
|
25143
25895
|
init_inbound();
|
|
25896
|
+
init_perf_tracer();
|
|
25144
25897
|
init_bootstrap();
|
|
25145
25898
|
if (false) {}
|
|
25146
25899
|
});
|
|
@@ -25483,7 +26236,7 @@ async function checkOrchestrationHealth(options) {
|
|
|
25483
26236
|
// src/doctor/checks/runtime-check.ts
|
|
25484
26237
|
import { constants } from "node:fs";
|
|
25485
26238
|
import { access as access4, stat as stat3 } from "node:fs/promises";
|
|
25486
|
-
import { dirname as
|
|
26239
|
+
import { dirname as dirname14 } from "node:path";
|
|
25487
26240
|
import { homedir as homedir11 } from "node:os";
|
|
25488
26241
|
async function checkRuntime(options = {}) {
|
|
25489
26242
|
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
@@ -25580,7 +26333,7 @@ async function checkFileCreatable(label, path14, probe, platform) {
|
|
|
25580
26333
|
detail: `${label}: ${path14} (unusable: ${formatError6(error2)})`
|
|
25581
26334
|
};
|
|
25582
26335
|
}
|
|
25583
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
26336
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname14(path14), probe, platform);
|
|
25584
26337
|
if (!parentCheck.ok) {
|
|
25585
26338
|
return {
|
|
25586
26339
|
ok: false,
|
|
@@ -25616,7 +26369,7 @@ async function checkCreatableAncestorDirectory(path14, probe, platform) {
|
|
|
25616
26369
|
blockingPath: path14
|
|
25617
26370
|
};
|
|
25618
26371
|
}
|
|
25619
|
-
const parent =
|
|
26372
|
+
const parent = dirname14(path14);
|
|
25620
26373
|
if (parent === path14) {
|
|
25621
26374
|
return {
|
|
25622
26375
|
ok: false,
|
|
@@ -26194,11 +26947,12 @@ var init_doctor2 = __esm(async () => {
|
|
|
26194
26947
|
init_config_store();
|
|
26195
26948
|
init_load_config();
|
|
26196
26949
|
init_ensure_config();
|
|
26950
|
+
init_agent_templates();
|
|
26197
26951
|
init_create_daemon_controller();
|
|
26198
26952
|
init_daemon_files();
|
|
26199
26953
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26200
26954
|
import { homedir as homedir13 } from "node:os";
|
|
26201
|
-
import { dirname as
|
|
26955
|
+
import { dirname as dirname15, join as join16, sep } from "node:path";
|
|
26202
26956
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
26203
26957
|
|
|
26204
26958
|
// src/daemon/daemon-runtime.ts
|
|
@@ -38648,6 +39402,7 @@ var taskStatusSchema = exports_external.enum([
|
|
|
38648
39402
|
var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
|
|
38649
39403
|
var orderSchema = exports_external.enum(["asc", "desc"]);
|
|
38650
39404
|
var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
|
|
39405
|
+
var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
|
|
38651
39406
|
var taskQuestionSchema = exports_external.object({
|
|
38652
39407
|
taskId: exports_external.string().min(1),
|
|
38653
39408
|
questionId: exports_external.string().min(1)
|
|
@@ -38657,7 +39412,8 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38657
39412
|
const tools = [
|
|
38658
39413
|
{
|
|
38659
39414
|
name: "delegate_request",
|
|
38660
|
-
description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker.
|
|
39415
|
+
description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker. Supports MCP Tasks when the client requests task execution: the tool can return a native task handle immediately, then clients can use tasks/get, tasks/result, tasks/list, and tasks/cancel. For legacy clients, after this returns status=running, keep the returned taskId and use task_get/task_list for non-blocking progress snapshots; call task_wait only when the user explicitly wants you to wait/block until completion or attention is required. 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(", ")}.` : ""}`,
|
|
39416
|
+
execution: { taskSupport: "optional" },
|
|
38661
39417
|
inputSchema: exports_external.object({
|
|
38662
39418
|
targetAgent: exports_external.string().min(1),
|
|
38663
39419
|
task: exports_external.string().min(1),
|
|
@@ -38740,7 +39496,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38740
39496
|
},
|
|
38741
39497
|
{
|
|
38742
39498
|
name: "task_get",
|
|
38743
|
-
description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use
|
|
39499
|
+
description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use to inspect a task snapshot non-blockingly, read the actual output after completion, or inspect a task that requires attention.",
|
|
38744
39500
|
inputSchema: exports_external.object({
|
|
38745
39501
|
taskId: exports_external.string().min(1)
|
|
38746
39502
|
}).strict(),
|
|
@@ -38775,7 +39531,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38775
39531
|
},
|
|
38776
39532
|
{
|
|
38777
39533
|
name: "task_approve",
|
|
38778
|
-
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,
|
|
39534
|
+
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, use task_get/task_list for snapshots or task_wait only if intentionally blocking.",
|
|
38779
39535
|
inputSchema: exports_external.object({
|
|
38780
39536
|
taskId: exports_external.string().min(1)
|
|
38781
39537
|
}).strict(),
|
|
@@ -38817,7 +39573,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38817
39573
|
},
|
|
38818
39574
|
{
|
|
38819
39575
|
name: "task_wait",
|
|
38820
|
-
description: `Wait for a task to finish or require attention.
|
|
39576
|
+
description: `Wait for a task to finish or require attention. This is a blocking legacy compatibility tool; do not call it automatically after delegate_request when the user only asked to start/delegate work. Use task_get/task_list for non-blocking progress snapshots; call task_wait when the user explicitly asks to wait, or when your next step truly depends on completion. 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, use task_get/task_list snapshots or task_wait only if intentionally blocking), or status=timeout (still running; use task_get for a snapshot, or task_wait only if intentionally blocking). 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.`,
|
|
38821
39577
|
inputSchema: exports_external.object({
|
|
38822
39578
|
taskId: exports_external.string().min(1),
|
|
38823
39579
|
timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
|
|
@@ -38831,9 +39587,29 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38831
39587
|
return createSuccessResult(renderTaskWaitResult(result), result);
|
|
38832
39588
|
})
|
|
38833
39589
|
},
|
|
39590
|
+
{
|
|
39591
|
+
name: "task_watch",
|
|
39592
|
+
description: `Long-poll a task for the next event, attention-required state, or terminal state. This is the recommended legacy way to watch a delegated task without repeatedly calling task_wait. For MCP-task-capable clients, request task execution for this tool to create a background watcher: the call returns a native task handle immediately, and tasks/result returns when the watch condition is met. The native watcher is single-shot: it runs one watch cycle then terminates, so to keep watching start another task_watch with afterSeq set to the returned nextAfterSeq. Defaults: timeout ${DEFAULT_TASK_WATCH_TIMEOUT_MS} ms, poll interval ${DEFAULT_TASK_WATCH_POLL_INTERVAL_MS} ms. Maximums: timeout ${MAX_TASK_WATCH_TIMEOUT_MS} ms, poll interval ${MAX_TASK_WATCH_POLL_INTERVAL_MS} ms.`,
|
|
39593
|
+
execution: { taskSupport: "optional" },
|
|
39594
|
+
inputSchema: exports_external.object({
|
|
39595
|
+
taskId: exports_external.string().min(1),
|
|
39596
|
+
afterSeq: exports_external.number().int().min(0).optional(),
|
|
39597
|
+
mode: taskWatchModeSchema.optional(),
|
|
39598
|
+
includeProgress: exports_external.boolean().optional(),
|
|
39599
|
+
timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WATCH_TIMEOUT_MS).optional(),
|
|
39600
|
+
pollIntervalMs: exports_external.number().int().min(1).max(MAX_TASK_WATCH_POLL_INTERVAL_MS).optional()
|
|
39601
|
+
}).strict(),
|
|
39602
|
+
handler: async (args) => await asToolResult(async () => {
|
|
39603
|
+
const result = await transport.watchTask({
|
|
39604
|
+
coordinatorSession,
|
|
39605
|
+
...args
|
|
39606
|
+
});
|
|
39607
|
+
return createSuccessResult(renderTaskWatchResult(result), result);
|
|
39608
|
+
})
|
|
39609
|
+
},
|
|
38834
39610
|
{
|
|
38835
39611
|
name: "worker_raise_question",
|
|
38836
|
-
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
|
|
39612
|
+
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_get/task_list for snapshots, or task_wait only if intentionally blocking.",
|
|
38837
39613
|
inputSchema: exports_external.object({
|
|
38838
39614
|
taskId: exports_external.string().min(1),
|
|
38839
39615
|
question: exports_external.string().min(1),
|
|
@@ -38853,7 +39629,7 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
38853
39629
|
},
|
|
38854
39630
|
{
|
|
38855
39631
|
name: "coordinator_answer_question",
|
|
38856
|
-
description: "Answer a blocked worker question under the current coordinator. Use
|
|
39632
|
+
description: "Answer a blocked worker question under the current coordinator. Use when task_get shows a pending question; after answering, use task_get/task_list for snapshots or task_wait only if intentionally blocking for the worker to finish.",
|
|
38857
39633
|
inputSchema: exports_external.object({
|
|
38858
39634
|
taskId: exports_external.string().min(1),
|
|
38859
39635
|
questionId: exports_external.string().min(1),
|
|
@@ -38957,7 +39733,7 @@ function renderTaskWaitResult(result) {
|
|
|
38957
39733
|
if (result.status === "timeout") {
|
|
38958
39734
|
return [
|
|
38959
39735
|
`Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`,
|
|
38960
|
-
`Next: call
|
|
39736
|
+
`Next: call task_get for a snapshot, or call task_wait again only if you intentionally want to keep blocking.`
|
|
38961
39737
|
].join(`
|
|
38962
39738
|
`);
|
|
38963
39739
|
}
|
|
@@ -38968,7 +39744,7 @@ function renderTaskWaitResult(result) {
|
|
|
38968
39744
|
` - status=needs_confirmation -> task_approve or task_reject`,
|
|
38969
39745
|
` - status=blocked or waiting_for_human -> coordinator_answer_question`,
|
|
38970
39746
|
` - reviewPending set -> coordinator_review_contested_result`,
|
|
38971
|
-
`After resolving, call task_wait again
|
|
39747
|
+
`After resolving, use task_get/task_list for snapshots, or call task_wait again only if you intentionally want to keep blocking.`
|
|
38972
39748
|
].join(`
|
|
38973
39749
|
`);
|
|
38974
39750
|
}
|
|
@@ -38978,6 +39754,26 @@ function renderTaskWaitResult(result) {
|
|
|
38978
39754
|
].join(`
|
|
38979
39755
|
`);
|
|
38980
39756
|
}
|
|
39757
|
+
function renderTaskWatchResult(result) {
|
|
39758
|
+
if (result.status === "not_found" || !result.task) {
|
|
39759
|
+
return "Task not found.";
|
|
39760
|
+
}
|
|
39761
|
+
const header = [
|
|
39762
|
+
`Task ${result.task.taskId} watch ${result.status.replace("_", " ")}; current state is ${result.task.status}.`,
|
|
39763
|
+
`- nextAfterSeq: ${result.nextAfterSeq}`,
|
|
39764
|
+
result.historyTruncated ? "- historyTruncated: true" : ""
|
|
39765
|
+
].filter((line) => line.length > 0);
|
|
39766
|
+
const events = result.events.length > 0 ? [
|
|
39767
|
+
"- Events:",
|
|
39768
|
+
...result.events.map((event) => {
|
|
39769
|
+
const detail = event.summary ?? event.message ?? event.status ?? "";
|
|
39770
|
+
return ` - #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
|
|
39771
|
+
})
|
|
39772
|
+
] : ["- Events: none"];
|
|
39773
|
+
const next = result.status === "terminal" ? "Next: call task_get to read the final result." : result.status === "attention_required" ? "Next: call task_get to inspect openQuestion / reviewPending, then resolve it with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
|
|
39774
|
+
return [...header, ...events, next].join(`
|
|
39775
|
+
`);
|
|
39776
|
+
}
|
|
38981
39777
|
function createSuccessResult(text, structuredContent) {
|
|
38982
39778
|
return {
|
|
38983
39779
|
content: [{ type: "text", text }],
|
|
@@ -38991,7 +39787,7 @@ function createErrorResult(message) {
|
|
|
38991
39787
|
};
|
|
38992
39788
|
}
|
|
38993
39789
|
function renderDelegateSuccess(result) {
|
|
38994
|
-
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:
|
|
39790
|
+
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: task "${result.taskId}" is running. Return this taskId to the user, or call task_get/task_list for non-blocking progress snapshots (or task_watch to long-poll for the next event). Call task_wait only if the user explicitly asks you to wait/block until completion.`;
|
|
38995
39791
|
return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
|
|
38996
39792
|
`);
|
|
38997
39793
|
}
|
|
@@ -39096,6 +39892,8 @@ function renderTaskSummary(task) {
|
|
|
39096
39892
|
header.push(`- Task: ${task.task}`);
|
|
39097
39893
|
if (task.summary.trim().length > 0)
|
|
39098
39894
|
header.push(`- Summary: ${task.summary}`);
|
|
39895
|
+
if (task.lastProgressSummary)
|
|
39896
|
+
header.push(`- Latest progress: ${task.lastProgressSummary}`);
|
|
39099
39897
|
if (task.resultText.trim().length > 0)
|
|
39100
39898
|
header.push(`- Result: ${task.resultText}`);
|
|
39101
39899
|
const events = [];
|
|
@@ -39140,7 +39938,7 @@ function renderTaskApprovalSuccess(task) {
|
|
|
39140
39938
|
return [
|
|
39141
39939
|
`Task "${task.taskId}" approved.`,
|
|
39142
39940
|
`- Current status: ${task.status}`,
|
|
39143
|
-
`Next: call task_wait
|
|
39941
|
+
`Next: use task_get/task_list for non-blocking progress snapshots, or call task_wait only if you intentionally want to block until the worker finishes; then task_get to read the final result.`
|
|
39144
39942
|
].join(`
|
|
39145
39943
|
`);
|
|
39146
39944
|
}
|
|
@@ -39210,6 +40008,9 @@ class OrchestrationClient {
|
|
|
39210
40008
|
async waitTask(input) {
|
|
39211
40009
|
return await this.request("task.wait", input, getWaitRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
|
|
39212
40010
|
}
|
|
40011
|
+
async watchTask(input) {
|
|
40012
|
+
return await this.request("task.watch", input, getWatchRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
|
|
40013
|
+
}
|
|
39213
40014
|
async approveTask(input) {
|
|
39214
40015
|
return await this.request("task.approve", input);
|
|
39215
40016
|
}
|
|
@@ -39326,6 +40127,11 @@ function getWaitRequestTimeoutMs(waitTimeoutMs, defaultTimeoutMs) {
|
|
|
39326
40127
|
const boundedWaitTimeoutMs = Math.min(Math.max(Math.floor(requestedWaitTimeoutMs ?? DEFAULT_TASK_WAIT_TIMEOUT_MS), 0), MAX_TASK_WAIT_TIMEOUT_MS);
|
|
39327
40128
|
return Math.max(defaultTimeoutMs, boundedWaitTimeoutMs + TASK_WAIT_RPC_TIMEOUT_PADDING_MS);
|
|
39328
40129
|
}
|
|
40130
|
+
function getWatchRequestTimeoutMs(watchTimeoutMs, defaultTimeoutMs) {
|
|
40131
|
+
const requestedWatchTimeoutMs = watchTimeoutMs === undefined ? undefined : Number.isFinite(watchTimeoutMs) ? watchTimeoutMs : 0;
|
|
40132
|
+
const boundedWatchTimeoutMs = Math.min(Math.max(Math.floor(requestedWatchTimeoutMs ?? DEFAULT_TASK_WATCH_TIMEOUT_MS), 0), MAX_TASK_WATCH_TIMEOUT_MS);
|
|
40133
|
+
return Math.max(defaultTimeoutMs, boundedWatchTimeoutMs + TASK_WATCH_RPC_TIMEOUT_PADDING_MS);
|
|
40134
|
+
}
|
|
39329
40135
|
|
|
39330
40136
|
// src/mcp/weacpx-mcp-transport.ts
|
|
39331
40137
|
function createOrchestrationTransport(endpoint, deps = {}) {
|
|
@@ -39355,6 +40161,7 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
39355
40161
|
rejectTask: async (input) => await client.rejectTask(input),
|
|
39356
40162
|
cancelTask: async (input) => await client.cancelTaskForCoordinator(input),
|
|
39357
40163
|
waitTask: async (input) => await client.waitTask(input),
|
|
40164
|
+
watchTask: async (input) => await client.watchTask(input),
|
|
39358
40165
|
workerRaiseQuestion: async (input) => {
|
|
39359
40166
|
const sourceHandle = input.sourceHandle.trim();
|
|
39360
40167
|
if (sourceHandle.length === 0) {
|
|
@@ -39376,21 +40183,31 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
39376
40183
|
}
|
|
39377
40184
|
|
|
39378
40185
|
// src/mcp/weacpx-mcp-server.ts
|
|
40186
|
+
var TASK_OPTIONS_CACHE_LIMIT = 1000;
|
|
40187
|
+
var TASKS_LIST_PAGE_SIZE = 100;
|
|
40188
|
+
var WATCH_TASKS_CACHE_LIMIT = 256;
|
|
39379
40189
|
var WEACPX_MCP_SERVER_INSTRUCTIONS = [
|
|
39380
40190
|
"Use these tools to orchestrate work across other agents under your coordinator session.",
|
|
39381
40191
|
"",
|
|
39382
40192
|
"Typical lifecycle for a single delegation:",
|
|
40193
|
+
"Preferred MCP Tasks lifecycle (for clients that support task-augmented tools/call):",
|
|
40194
|
+
"1. Call delegate_request with task execution requested. It returns a native MCP task handle immediately.",
|
|
40195
|
+
"2. Use task_watch with MCP task execution to start a background watcher, or use tasks/get / tasks/list to poll status. Use tasks/result after terminal status, or on input_required to receive an actionable next-step package; use tasks/cancel to cancel.",
|
|
40196
|
+
" - When tasks/result returns input_required, that result stream is complete. Call the recommended tool, then resume polling with tasks/get / tasks/result.",
|
|
40197
|
+
"3. Status mapping: working = running, input_required = needs_confirmation / blocked / waiting_for_human / contested review, completed / failed / cancelled are terminal.",
|
|
40198
|
+
"",
|
|
40199
|
+
"Legacy tool lifecycle for clients without MCP Tasks support:",
|
|
39383
40200
|
"1. delegate_request → returns { taskId, status }.",
|
|
39384
|
-
" - status=running: the worker has started; go to step 2.",
|
|
39385
|
-
" - status=needs_confirmation: tell the user, then call task_approve or task_reject based on their response. After task_approve,
|
|
39386
|
-
"2. task_wait(taskId) → blocks until the task is done, needs attention, or times out.",
|
|
40201
|
+
" - status=running: the worker has started. Return the taskId to the user, use task_get / task_list for non-blocking snapshots, or task_watch to long-poll for the next event; only go to step 2 when you intentionally want to block waiting.",
|
|
40202
|
+
" - status=needs_confirmation: tell the user, then call task_approve or task_reject based on their response. After task_approve, use task_get/task_list for snapshots or step 2 only if intentionally blocking. Do not call task_wait before approval.",
|
|
40203
|
+
"2. Optional blocking wait: task_wait(taskId) → blocks until the task is done, needs attention, or times out. Do not call it automatically when the user asked to delegate and continue.",
|
|
39387
40204
|
" - status=terminal: go to step 3.",
|
|
39388
40205
|
" - 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:",
|
|
39389
|
-
" * needs_confirmation -> task_approve or task_reject (after approval,
|
|
40206
|
+
" * needs_confirmation -> task_approve or task_reject (after approval, use snapshots or optional blocking wait only if needed)",
|
|
39390
40207
|
" * blocked or waiting_for_human -> coordinator_answer_question (the answer can come from you or be relayed from a human you consulted)",
|
|
39391
40208
|
" * reviewPending set -> coordinator_review_contested_result with accept or discard",
|
|
39392
|
-
" After resolving,
|
|
39393
|
-
" - status=timeout: the task is still running.
|
|
40209
|
+
" After resolving, use task_get / task_list for snapshots, or step 2 only if intentionally blocking.",
|
|
40210
|
+
" - status=timeout: the task is still running. Use task_get for a snapshot, or call task_wait again only if you still intentionally want to block.",
|
|
39394
40211
|
"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.",
|
|
39395
40212
|
"",
|
|
39396
40213
|
"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.",
|
|
@@ -39401,18 +40218,27 @@ var WEACPX_MCP_SERVER_INSTRUCTIONS = [
|
|
|
39401
40218
|
].join(`
|
|
39402
40219
|
`);
|
|
39403
40220
|
function createWeacpxMcpServer(options) {
|
|
40221
|
+
let getToolState;
|
|
40222
|
+
const taskOptionsById = new Map;
|
|
40223
|
+
const watchTasksById = new Map;
|
|
39404
40224
|
const server = new Server({
|
|
39405
40225
|
name: "weacpx-orchestration",
|
|
39406
40226
|
version: readVersion()
|
|
39407
40227
|
}, {
|
|
39408
40228
|
capabilities: {
|
|
39409
|
-
tools: {}
|
|
40229
|
+
tools: {},
|
|
40230
|
+
tasks: {
|
|
40231
|
+
list: {},
|
|
40232
|
+
cancel: {},
|
|
40233
|
+
requests: { tools: { call: {} } }
|
|
40234
|
+
}
|
|
39410
40235
|
},
|
|
39411
|
-
instructions: WEACPX_MCP_SERVER_INSTRUCTIONS
|
|
40236
|
+
instructions: WEACPX_MCP_SERVER_INSTRUCTIONS,
|
|
40237
|
+
taskStore: createWeacpxTaskStore(async () => await getToolState(), taskOptionsById, watchTasksById)
|
|
39412
40238
|
});
|
|
39413
40239
|
let toolState = null;
|
|
39414
40240
|
let toolStatePromise = null;
|
|
39415
|
-
async function
|
|
40241
|
+
getToolState = async function getToolState2() {
|
|
39416
40242
|
if (toolState) {
|
|
39417
40243
|
return toolState;
|
|
39418
40244
|
}
|
|
@@ -39435,14 +40261,15 @@ function createWeacpxMcpServer(options) {
|
|
|
39435
40261
|
toolStatePromise = null;
|
|
39436
40262
|
});
|
|
39437
40263
|
return await toolStatePromise;
|
|
39438
|
-
}
|
|
40264
|
+
};
|
|
39439
40265
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
39440
40266
|
const tools = (await getToolState()).tools;
|
|
39441
40267
|
return {
|
|
39442
40268
|
tools: tools.map((tool) => ({
|
|
39443
40269
|
name: tool.name,
|
|
39444
40270
|
description: tool.description,
|
|
39445
|
-
inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema))
|
|
40271
|
+
inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema)),
|
|
40272
|
+
...tool.execution ? { execution: tool.execution } : {}
|
|
39446
40273
|
}))
|
|
39447
40274
|
};
|
|
39448
40275
|
});
|
|
@@ -39456,15 +40283,399 @@ function createWeacpxMcpServer(options) {
|
|
|
39456
40283
|
if (!parsed.success) {
|
|
39457
40284
|
throw new McpError(ErrorCode.InvalidParams, formatZodError(parsed.error));
|
|
39458
40285
|
}
|
|
40286
|
+
if (request.params.task) {
|
|
40287
|
+
if (tool.name !== "delegate_request" && tool.name !== "task_watch") {
|
|
40288
|
+
throw new McpError(ErrorCode.InvalidParams, `Tool ${tool.name} does not support MCP task execution`);
|
|
40289
|
+
}
|
|
40290
|
+
if (tool.name === "delegate_request") {
|
|
40291
|
+
return await createDelegationMcpTask({
|
|
40292
|
+
state: await getToolState(),
|
|
40293
|
+
args: parsed.data,
|
|
40294
|
+
taskParams: request.params.task,
|
|
40295
|
+
taskOptionsById
|
|
40296
|
+
});
|
|
40297
|
+
}
|
|
40298
|
+
return await createWatchMcpTask({
|
|
40299
|
+
state: await getToolState(),
|
|
40300
|
+
args: parsed.data,
|
|
40301
|
+
taskParams: request.params.task,
|
|
40302
|
+
taskOptionsById,
|
|
40303
|
+
watchTasksById
|
|
40304
|
+
});
|
|
40305
|
+
}
|
|
39459
40306
|
return await tool.handler(parsed.data);
|
|
39460
40307
|
});
|
|
40308
|
+
server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
|
|
40309
|
+
const watchTask = watchTasksById.get(request.params.taskId);
|
|
40310
|
+
if (watchTask) {
|
|
40311
|
+
if (!watchTask.result) {
|
|
40312
|
+
throw new McpError(ErrorCode.InvalidRequest, `Task ${request.params.taskId} is still ${watchTask.task.status}`);
|
|
40313
|
+
}
|
|
40314
|
+
watchTasksById.delete(request.params.taskId);
|
|
40315
|
+
return watchTask.result;
|
|
40316
|
+
}
|
|
40317
|
+
const state = await getToolState();
|
|
40318
|
+
const task = await state.transport.getTask({
|
|
40319
|
+
coordinatorSession: state.coordinatorSession,
|
|
40320
|
+
taskId: request.params.taskId
|
|
40321
|
+
});
|
|
40322
|
+
if (!task) {
|
|
40323
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${request.params.taskId}`);
|
|
40324
|
+
}
|
|
40325
|
+
return withRelatedTaskMeta(renderNativeTaskPayloadResult(task), task.taskId);
|
|
40326
|
+
});
|
|
39461
40327
|
return server;
|
|
39462
40328
|
}
|
|
39463
40329
|
function buildToolState(options) {
|
|
39464
40330
|
const tools = buildWeacpxMcpToolRegistry(options);
|
|
39465
40331
|
return {
|
|
39466
40332
|
tools,
|
|
39467
|
-
toolMap: new Map(tools.map((tool) => [tool.name, tool]))
|
|
40333
|
+
toolMap: new Map(tools.map((tool) => [tool.name, tool])),
|
|
40334
|
+
transport: options.transport,
|
|
40335
|
+
coordinatorSession: options.coordinatorSession,
|
|
40336
|
+
sourceHandle: options.sourceHandle
|
|
40337
|
+
};
|
|
40338
|
+
}
|
|
40339
|
+
async function createDelegationMcpTask(input) {
|
|
40340
|
+
const delegateTool = input.state.toolMap.get("delegate_request");
|
|
40341
|
+
if (!delegateTool) {
|
|
40342
|
+
throw new McpError(ErrorCode.MethodNotFound, "delegate_request is not registered");
|
|
40343
|
+
}
|
|
40344
|
+
const result = await delegateTool.handler(input.args);
|
|
40345
|
+
if (result.isError) {
|
|
40346
|
+
throw new McpError(ErrorCode.InvalidRequest, result.content.map((item) => item.type === "text" ? item.text : "").filter(Boolean).join(`
|
|
40347
|
+
`) || "Delegation failed");
|
|
40348
|
+
}
|
|
40349
|
+
const structured = result.structuredContent;
|
|
40350
|
+
const taskId = typeof structured?.taskId === "string" ? structured.taskId : undefined;
|
|
40351
|
+
if (!taskId) {
|
|
40352
|
+
throw new McpError(ErrorCode.InternalError, "delegate_request did not return a taskId");
|
|
40353
|
+
}
|
|
40354
|
+
rememberTaskOptions(input.taskOptionsById, taskId, input.taskParams);
|
|
40355
|
+
const task = await input.state.transport.getTask({
|
|
40356
|
+
coordinatorSession: input.state.coordinatorSession,
|
|
40357
|
+
taskId
|
|
40358
|
+
});
|
|
40359
|
+
if (!task) {
|
|
40360
|
+
throw new McpError(ErrorCode.InternalError, `delegate_request created task "${taskId}" but it was not readable from orchestration state`);
|
|
40361
|
+
}
|
|
40362
|
+
return {
|
|
40363
|
+
task: toMcpTask(task, input.taskParams)
|
|
40364
|
+
};
|
|
40365
|
+
}
|
|
40366
|
+
async function createWatchMcpTask(input) {
|
|
40367
|
+
const taskId = input.args.taskId;
|
|
40368
|
+
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
40369
|
+
throw new McpError(ErrorCode.InvalidParams, "task_watch requires taskId");
|
|
40370
|
+
}
|
|
40371
|
+
const baseTask = await input.state.transport.getTask({
|
|
40372
|
+
coordinatorSession: input.state.coordinatorSession,
|
|
40373
|
+
taskId
|
|
40374
|
+
});
|
|
40375
|
+
if (!baseTask) {
|
|
40376
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
40377
|
+
}
|
|
40378
|
+
const now = new Date().toISOString();
|
|
40379
|
+
const watchTaskId = `watch:${taskId}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
40380
|
+
rememberTaskOptions(input.taskOptionsById, watchTaskId, input.taskParams);
|
|
40381
|
+
const watchTask = toMcpTask({
|
|
40382
|
+
taskId: watchTaskId,
|
|
40383
|
+
status: "running",
|
|
40384
|
+
summary: `Watching task ${taskId}`,
|
|
40385
|
+
createdAt: now,
|
|
40386
|
+
updatedAt: now
|
|
40387
|
+
}, input.taskParams);
|
|
40388
|
+
registerWatchTask(input.watchTasksById, watchTaskId, { task: watchTask });
|
|
40389
|
+
runWatchMcpTask({
|
|
40390
|
+
state: input.state,
|
|
40391
|
+
args: input.args,
|
|
40392
|
+
watchTaskId,
|
|
40393
|
+
taskOptions: input.taskParams,
|
|
40394
|
+
watchTasksById: input.watchTasksById
|
|
40395
|
+
});
|
|
40396
|
+
return {
|
|
40397
|
+
task: watchTask
|
|
40398
|
+
};
|
|
40399
|
+
}
|
|
40400
|
+
async function runWatchMcpTask(input) {
|
|
40401
|
+
const args = input.args;
|
|
40402
|
+
try {
|
|
40403
|
+
const result = await input.state.transport.watchTask({
|
|
40404
|
+
coordinatorSession: input.state.coordinatorSession,
|
|
40405
|
+
...args
|
|
40406
|
+
});
|
|
40407
|
+
if (!input.watchTasksById.has(input.watchTaskId))
|
|
40408
|
+
return;
|
|
40409
|
+
const now = new Date().toISOString();
|
|
40410
|
+
const mcpStatus = result.status === "attention_required" ? "input_required" : result.status === "not_found" ? "failed" : "completed";
|
|
40411
|
+
input.watchTasksById.set(input.watchTaskId, {
|
|
40412
|
+
task: {
|
|
40413
|
+
taskId: input.watchTaskId,
|
|
40414
|
+
status: mcpStatus,
|
|
40415
|
+
ttl: input.taskOptions.ttl ?? null,
|
|
40416
|
+
createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
|
|
40417
|
+
lastUpdatedAt: now,
|
|
40418
|
+
...input.taskOptions.pollInterval !== undefined ? { pollInterval: input.taskOptions.pollInterval } : {},
|
|
40419
|
+
statusMessage: renderWatchTaskStatusMessage(result)
|
|
40420
|
+
},
|
|
40421
|
+
result: withRelatedTaskMeta(renderWatchMcpTaskResult(result, input.watchTaskId), result.task?.taskId ?? input.watchTaskId)
|
|
40422
|
+
});
|
|
40423
|
+
} catch (error2) {
|
|
40424
|
+
if (!input.watchTasksById.has(input.watchTaskId))
|
|
40425
|
+
return;
|
|
40426
|
+
const now = new Date().toISOString();
|
|
40427
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
40428
|
+
input.watchTasksById.set(input.watchTaskId, {
|
|
40429
|
+
task: {
|
|
40430
|
+
taskId: input.watchTaskId,
|
|
40431
|
+
status: "failed",
|
|
40432
|
+
ttl: input.taskOptions.ttl ?? null,
|
|
40433
|
+
createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
|
|
40434
|
+
lastUpdatedAt: now,
|
|
40435
|
+
statusMessage: message
|
|
40436
|
+
},
|
|
40437
|
+
result: {
|
|
40438
|
+
content: [{ type: "text", text: `Task watch "${input.watchTaskId}" failed: ${message}` }],
|
|
40439
|
+
structuredContent: { watchTaskId: input.watchTaskId, error: message },
|
|
40440
|
+
isError: true
|
|
40441
|
+
}
|
|
40442
|
+
});
|
|
40443
|
+
}
|
|
40444
|
+
}
|
|
40445
|
+
function renderWatchTaskStatusMessage(result) {
|
|
40446
|
+
if (!result.task)
|
|
40447
|
+
return `Watch finished: ${result.status}`;
|
|
40448
|
+
return `Watch finished for ${result.task.taskId}: ${result.status}; task status ${result.task.status}; events ${result.events?.length ?? 0}`;
|
|
40449
|
+
}
|
|
40450
|
+
function renderWatchMcpTaskResult(result, watchTaskId) {
|
|
40451
|
+
if (result.status === "not_found" || !result.task) {
|
|
40452
|
+
return {
|
|
40453
|
+
content: [{ type: "text", text: `Task watch "${watchTaskId}" finished: watched task not found.` }],
|
|
40454
|
+
structuredContent: { watchTaskId, ...result },
|
|
40455
|
+
isError: true
|
|
40456
|
+
};
|
|
40457
|
+
}
|
|
40458
|
+
const header = [
|
|
40459
|
+
`Task watch "${watchTaskId}" finished with ${result.status.replace("_", " ")}.`,
|
|
40460
|
+
`Watched task ${result.task.taskId} is ${result.task.status}.`,
|
|
40461
|
+
`nextAfterSeq: ${result.nextAfterSeq}`,
|
|
40462
|
+
result.historyTruncated ? "historyTruncated: true" : ""
|
|
40463
|
+
].filter((line) => line.length > 0);
|
|
40464
|
+
const events = result.events.length > 0 ? [
|
|
40465
|
+
"Events:",
|
|
40466
|
+
...result.events.map((event) => {
|
|
40467
|
+
const detail = event.summary ?? event.message ?? event.status ?? "";
|
|
40468
|
+
return `- #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
|
|
40469
|
+
})
|
|
40470
|
+
] : ["Events: none"];
|
|
40471
|
+
const next = result.status === "terminal" ? "Next: call task_get on the watched task to read the final result." : result.status === "attention_required" ? "Next: call task_get on the watched task, then resolve openQuestion / reviewPending with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
|
|
40472
|
+
return {
|
|
40473
|
+
content: [{ type: "text", text: [...header, ...events, next].join(`
|
|
40474
|
+
`) }],
|
|
40475
|
+
structuredContent: { watchTaskId, ...result }
|
|
40476
|
+
};
|
|
40477
|
+
}
|
|
40478
|
+
function createWeacpxTaskStore(resolveState, taskOptionsById, watchTasksById) {
|
|
40479
|
+
return {
|
|
40480
|
+
createTask: async () => {
|
|
40481
|
+
throw new Error("weacpx native MCP tasks are created by delegate_request");
|
|
40482
|
+
},
|
|
40483
|
+
getTask: async (taskId) => {
|
|
40484
|
+
const watchTask = watchTasksById.get(taskId);
|
|
40485
|
+
if (watchTask)
|
|
40486
|
+
return watchTask.task;
|
|
40487
|
+
const state = await resolveState();
|
|
40488
|
+
const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
|
|
40489
|
+
return task ? toMcpTask(task, taskOptionsById.get(taskId)) : null;
|
|
40490
|
+
},
|
|
40491
|
+
storeTaskResult: async () => {
|
|
40492
|
+
throw new Error("weacpx native MCP task results are stored by orchestration");
|
|
40493
|
+
},
|
|
40494
|
+
getTaskResult: async (taskId) => {
|
|
40495
|
+
const watchTask = watchTasksById.get(taskId);
|
|
40496
|
+
if (watchTask) {
|
|
40497
|
+
if (!watchTask.result) {
|
|
40498
|
+
throw new Error(`Task ${taskId} is still ${watchTask.task.status}`);
|
|
40499
|
+
}
|
|
40500
|
+
watchTasksById.delete(taskId);
|
|
40501
|
+
return watchTask.result;
|
|
40502
|
+
}
|
|
40503
|
+
const state = await resolveState();
|
|
40504
|
+
const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
|
|
40505
|
+
if (!task) {
|
|
40506
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
40507
|
+
}
|
|
40508
|
+
return renderNativeTaskPayloadResult(task);
|
|
40509
|
+
},
|
|
40510
|
+
updateTaskStatus: async (taskId, status, statusMessage) => {
|
|
40511
|
+
const state = await resolveState();
|
|
40512
|
+
if (status === "cancelled") {
|
|
40513
|
+
await state.transport.cancelTask({ coordinatorSession: state.coordinatorSession, taskId });
|
|
40514
|
+
return;
|
|
40515
|
+
}
|
|
40516
|
+
throw new Error(`weacpx MCP task status is read-only (${status}${statusMessage ? `: ${statusMessage}` : ""})`);
|
|
40517
|
+
},
|
|
40518
|
+
listTasks: async (cursor) => {
|
|
40519
|
+
const state = await resolveState();
|
|
40520
|
+
const tasks = await state.transport.listTasks({
|
|
40521
|
+
coordinatorSession: state.coordinatorSession,
|
|
40522
|
+
sort: "updatedAt",
|
|
40523
|
+
order: "desc"
|
|
40524
|
+
});
|
|
40525
|
+
const watchTasks = Array.from(watchTasksById.values()).map((record3) => record3.task);
|
|
40526
|
+
pruneTaskOptions(taskOptionsById, new Set([...tasks.map((task) => task.taskId), ...watchTasks.map((task) => task.taskId)]));
|
|
40527
|
+
const offset = parseTaskListCursor(cursor);
|
|
40528
|
+
const allTasks = [
|
|
40529
|
+
...watchTasks,
|
|
40530
|
+
...tasks.map((task) => toMcpTask(task, taskOptionsById.get(task.taskId)))
|
|
40531
|
+
].sort((a, b) => b.lastUpdatedAt.localeCompare(a.lastUpdatedAt));
|
|
40532
|
+
const page = allTasks.slice(offset, offset + TASKS_LIST_PAGE_SIZE);
|
|
40533
|
+
const nextOffset = offset + page.length;
|
|
40534
|
+
return {
|
|
40535
|
+
tasks: page,
|
|
40536
|
+
...nextOffset < allTasks.length ? { nextCursor: String(nextOffset) } : {}
|
|
40537
|
+
};
|
|
40538
|
+
}
|
|
40539
|
+
};
|
|
40540
|
+
}
|
|
40541
|
+
function rememberTaskOptions(taskOptionsById, taskId, options) {
|
|
40542
|
+
taskOptionsById.set(taskId, normalizeCreateTaskOptions(options));
|
|
40543
|
+
while (taskOptionsById.size > TASK_OPTIONS_CACHE_LIMIT) {
|
|
40544
|
+
const oldestKey = taskOptionsById.keys().next().value;
|
|
40545
|
+
if (oldestKey === undefined)
|
|
40546
|
+
break;
|
|
40547
|
+
taskOptionsById.delete(oldestKey);
|
|
40548
|
+
}
|
|
40549
|
+
}
|
|
40550
|
+
function registerWatchTask(watchTasksById, watchTaskId, record3) {
|
|
40551
|
+
watchTasksById.set(watchTaskId, record3);
|
|
40552
|
+
while (watchTasksById.size > WATCH_TASKS_CACHE_LIMIT) {
|
|
40553
|
+
const oldestKey = watchTasksById.keys().next().value;
|
|
40554
|
+
if (oldestKey === undefined || oldestKey === watchTaskId)
|
|
40555
|
+
break;
|
|
40556
|
+
watchTasksById.delete(oldestKey);
|
|
40557
|
+
}
|
|
40558
|
+
}
|
|
40559
|
+
function pruneTaskOptions(taskOptionsById, taskIds) {
|
|
40560
|
+
for (const taskId of taskOptionsById.keys()) {
|
|
40561
|
+
if (!taskIds.has(taskId)) {
|
|
40562
|
+
taskOptionsById.delete(taskId);
|
|
40563
|
+
}
|
|
40564
|
+
}
|
|
40565
|
+
}
|
|
40566
|
+
function parseTaskListCursor(cursor) {
|
|
40567
|
+
if (!cursor)
|
|
40568
|
+
return 0;
|
|
40569
|
+
const offset = Number(cursor);
|
|
40570
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
40571
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tasks/list cursor: ${cursor}`);
|
|
40572
|
+
}
|
|
40573
|
+
return offset;
|
|
40574
|
+
}
|
|
40575
|
+
function renderNativeTaskPayloadResult(task) {
|
|
40576
|
+
if (toMcpTaskStatus(task) === "input_required") {
|
|
40577
|
+
return renderInputRequiredTaskResult(task);
|
|
40578
|
+
}
|
|
40579
|
+
if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
|
|
40580
|
+
return renderNativeTaskResult(task);
|
|
40581
|
+
}
|
|
40582
|
+
throw new McpError(ErrorCode.InvalidRequest, `Task ${task.taskId} is still ${task.status}; use tasks/get until it is terminal or input_required`);
|
|
40583
|
+
}
|
|
40584
|
+
function withRelatedTaskMeta(result, taskId) {
|
|
40585
|
+
return {
|
|
40586
|
+
...result,
|
|
40587
|
+
_meta: {
|
|
40588
|
+
...result._meta,
|
|
40589
|
+
[RELATED_TASK_META_KEY]: { taskId }
|
|
40590
|
+
}
|
|
40591
|
+
};
|
|
40592
|
+
}
|
|
40593
|
+
function renderNativeTaskResult(task) {
|
|
40594
|
+
const isError = task.status === "failed" || task.status === "cancelled";
|
|
40595
|
+
const text = [
|
|
40596
|
+
`Task "${task.taskId}" finished with status ${task.status}.`,
|
|
40597
|
+
task.resultText.trim().length > 0 ? task.resultText : task.summary
|
|
40598
|
+
].filter((line) => line.trim().length > 0).join(`
|
|
40599
|
+
`);
|
|
40600
|
+
return {
|
|
40601
|
+
content: [{ type: "text", text }],
|
|
40602
|
+
structuredContent: { task },
|
|
40603
|
+
...isError ? { isError: true } : {}
|
|
40604
|
+
};
|
|
40605
|
+
}
|
|
40606
|
+
function renderInputRequiredTaskResult(task) {
|
|
40607
|
+
const actions = inputRequiredActions(task);
|
|
40608
|
+
const text = [
|
|
40609
|
+
`Task "${task.taskId}" requires input before it can continue.`,
|
|
40610
|
+
task.summary.trim().length > 0 ? task.summary : "",
|
|
40611
|
+
task.openQuestion ? `Open question: ${task.openQuestion.question}` : "",
|
|
40612
|
+
`Next: call task_get("${task.taskId}") to inspect details, then ${actions.join(" or ")}.`
|
|
40613
|
+
].filter((line) => line.trim().length > 0).join(`
|
|
40614
|
+
`);
|
|
40615
|
+
return {
|
|
40616
|
+
content: [{ type: "text", text }],
|
|
40617
|
+
structuredContent: {
|
|
40618
|
+
task,
|
|
40619
|
+
nextAction: {
|
|
40620
|
+
kind: "input_required",
|
|
40621
|
+
taskId: task.taskId,
|
|
40622
|
+
recommendedTools: actions
|
|
40623
|
+
}
|
|
40624
|
+
}
|
|
40625
|
+
};
|
|
40626
|
+
}
|
|
40627
|
+
function inputRequiredActions(task) {
|
|
40628
|
+
const actions = [];
|
|
40629
|
+
if (task.status === "needs_confirmation") {
|
|
40630
|
+
actions.push("task_approve", "task_reject");
|
|
40631
|
+
}
|
|
40632
|
+
if (task.status === "blocked" || task.status === "waiting_for_human" || task.openQuestion) {
|
|
40633
|
+
actions.push("coordinator_answer_question");
|
|
40634
|
+
}
|
|
40635
|
+
if (task.reviewPending) {
|
|
40636
|
+
actions.push("coordinator_review_contested_result");
|
|
40637
|
+
}
|
|
40638
|
+
return actions.length > 0 ? actions : ["task_get"];
|
|
40639
|
+
}
|
|
40640
|
+
function toMcpTask(task, options = {}) {
|
|
40641
|
+
const statusMessage = mcpTaskStatusMessage(task);
|
|
40642
|
+
return {
|
|
40643
|
+
taskId: task.taskId,
|
|
40644
|
+
status: toMcpTaskStatus(task),
|
|
40645
|
+
ttl: options.ttl ?? null,
|
|
40646
|
+
createdAt: task.createdAt,
|
|
40647
|
+
lastUpdatedAt: task.updatedAt,
|
|
40648
|
+
...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {},
|
|
40649
|
+
...statusMessage ? { statusMessage } : {}
|
|
40650
|
+
};
|
|
40651
|
+
}
|
|
40652
|
+
function mcpTaskStatusMessage(task) {
|
|
40653
|
+
const lines = [
|
|
40654
|
+
task.summary.trim().length > 0 ? task.summary : "",
|
|
40655
|
+
task.lastProgressSummary ? `Latest progress: ${task.lastProgressSummary}` : "",
|
|
40656
|
+
task.lastProgressAt ? `Last progress at: ${task.lastProgressAt}` : ""
|
|
40657
|
+
].filter((line) => line.trim().length > 0);
|
|
40658
|
+
return lines.length > 0 ? lines.join(`
|
|
40659
|
+
`) : undefined;
|
|
40660
|
+
}
|
|
40661
|
+
function toMcpTaskStatus(task) {
|
|
40662
|
+
if (task.reviewPending !== undefined)
|
|
40663
|
+
return "input_required";
|
|
40664
|
+
if (task.status === "completed")
|
|
40665
|
+
return "completed";
|
|
40666
|
+
if (task.status === "failed")
|
|
40667
|
+
return "failed";
|
|
40668
|
+
if (task.status === "cancelled")
|
|
40669
|
+
return "cancelled";
|
|
40670
|
+
if (task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human") {
|
|
40671
|
+
return "input_required";
|
|
40672
|
+
}
|
|
40673
|
+
return "working";
|
|
40674
|
+
}
|
|
40675
|
+
function normalizeCreateTaskOptions(options) {
|
|
40676
|
+
return {
|
|
40677
|
+
ttl: options.ttl ?? null,
|
|
40678
|
+
...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {}
|
|
39468
40679
|
};
|
|
39469
40680
|
}
|
|
39470
40681
|
async function resolveMcpIdentity(server, options) {
|
|
@@ -39743,7 +40954,7 @@ function sanitizeName(input, fallback) {
|
|
|
39743
40954
|
init_plugin_home();
|
|
39744
40955
|
import { spawn as spawn4 } from "node:child_process";
|
|
39745
40956
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
39746
|
-
import { dirname as
|
|
40957
|
+
import { dirname as dirname9, join as join7 } from "node:path";
|
|
39747
40958
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
39748
40959
|
|
|
39749
40960
|
// src/plugins/package-manager.ts
|
|
@@ -40021,8 +41232,8 @@ async function runInherit(command, args) {
|
|
|
40021
41232
|
}
|
|
40022
41233
|
async function readPackageName() {
|
|
40023
41234
|
try {
|
|
40024
|
-
const here =
|
|
40025
|
-
for (const candidate of [
|
|
41235
|
+
const here = dirname9(fileURLToPath2(import.meta.url));
|
|
41236
|
+
for (const candidate of [join7(here, "..", "package.json"), join7(here, "..", "..", "package.json")]) {
|
|
40026
41237
|
try {
|
|
40027
41238
|
const parsed = JSON.parse(await readFile7(candidate, "utf8"));
|
|
40028
41239
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
@@ -40648,7 +41859,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
|
|
|
40648
41859
|
// src/plugins/plugin-cli.ts
|
|
40649
41860
|
init_plugin_home();
|
|
40650
41861
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
40651
|
-
import { isAbsolute, join as
|
|
41862
|
+
import { isAbsolute, join as join9, resolve } from "node:path";
|
|
40652
41863
|
init_plugin_loader();
|
|
40653
41864
|
init_validate_plugin();
|
|
40654
41865
|
|
|
@@ -40658,13 +41869,13 @@ init_plugin_loader();
|
|
|
40658
41869
|
init_validate_plugin();
|
|
40659
41870
|
init_known_plugins();
|
|
40660
41871
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
40661
|
-
import { join as
|
|
41872
|
+
import { join as join8 } from "node:path";
|
|
40662
41873
|
function suggestedPluginPackageForChannel(type) {
|
|
40663
41874
|
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
40664
41875
|
}
|
|
40665
41876
|
async function readDependencyEntries(pluginHome) {
|
|
40666
41877
|
try {
|
|
40667
|
-
const raw = await readFile8(
|
|
41878
|
+
const raw = await readFile8(join8(pluginHome, "package.json"), "utf8");
|
|
40668
41879
|
const parsed = JSON.parse(raw);
|
|
40669
41880
|
const out = {};
|
|
40670
41881
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40766,7 +41977,7 @@ function looksLikePath(spec) {
|
|
|
40766
41977
|
}
|
|
40767
41978
|
async function readDependencyEntries2(pluginHome) {
|
|
40768
41979
|
try {
|
|
40769
|
-
const raw = await readFile9(
|
|
41980
|
+
const raw = await readFile9(join9(pluginHome, "package.json"), "utf8");
|
|
40770
41981
|
const parsed = JSON.parse(raw);
|
|
40771
41982
|
const out = {};
|
|
40772
41983
|
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
@@ -40792,7 +42003,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
|
|
|
40792
42003
|
return name;
|
|
40793
42004
|
}
|
|
40794
42005
|
try {
|
|
40795
|
-
const raw = await readFile9(
|
|
42006
|
+
const raw = await readFile9(join9(installSpec, "package.json"), "utf8");
|
|
40796
42007
|
const parsed = JSON.parse(raw);
|
|
40797
42008
|
if (typeof parsed.name === "string" && parsed.name.trim())
|
|
40798
42009
|
return parsed.name.trim();
|
|
@@ -41400,6 +42611,7 @@ var HELP_LINES = [
|
|
|
41400
42611
|
"weacpx plugin list|add|update|remove|enable|disable|doctor|known - 管理插件",
|
|
41401
42612
|
"weacpx doctor - 运行诊断",
|
|
41402
42613
|
"weacpx version - 查看版本",
|
|
42614
|
+
"weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
|
|
41403
42615
|
"weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
|
|
41404
42616
|
"weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
|
|
41405
42617
|
];
|
|
@@ -41477,6 +42689,17 @@ async function runCli(args, deps = {}) {
|
|
|
41477
42689
|
}
|
|
41478
42690
|
return result;
|
|
41479
42691
|
}
|
|
42692
|
+
case "agent":
|
|
42693
|
+
case "agents": {
|
|
42694
|
+
const result = await handleAgentCli(args.slice(1), { print });
|
|
42695
|
+
if (result === null) {
|
|
42696
|
+
for (const line of HELP_LINES) {
|
|
42697
|
+
print(line);
|
|
42698
|
+
}
|
|
42699
|
+
return 1;
|
|
42700
|
+
}
|
|
42701
|
+
return result;
|
|
42702
|
+
}
|
|
41480
42703
|
case "plugin": {
|
|
41481
42704
|
const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
|
|
41482
42705
|
print,
|
|
@@ -41718,6 +42941,93 @@ async function workspaceRemove(rawName, print) {
|
|
|
41718
42941
|
print(`工作区「${name}」已删除`);
|
|
41719
42942
|
return 0;
|
|
41720
42943
|
}
|
|
42944
|
+
async function handleAgentCli(args, deps) {
|
|
42945
|
+
const subcommand = args[0];
|
|
42946
|
+
switch (subcommand) {
|
|
42947
|
+
case "list":
|
|
42948
|
+
if (args.length !== 1)
|
|
42949
|
+
return null;
|
|
42950
|
+
return await agentList(deps.print);
|
|
42951
|
+
case "templates":
|
|
42952
|
+
if (args.length !== 1)
|
|
42953
|
+
return null;
|
|
42954
|
+
return agentTemplates(deps.print);
|
|
42955
|
+
case "add":
|
|
42956
|
+
if (args.length !== 2 || !args[1])
|
|
42957
|
+
return null;
|
|
42958
|
+
return await agentAdd(args[1], deps.print);
|
|
42959
|
+
case "rm":
|
|
42960
|
+
if (args.length !== 2 || !args[1])
|
|
42961
|
+
return null;
|
|
42962
|
+
return await agentRemove(args[1], deps.print);
|
|
42963
|
+
default:
|
|
42964
|
+
return null;
|
|
42965
|
+
}
|
|
42966
|
+
}
|
|
42967
|
+
async function agentList(print) {
|
|
42968
|
+
const store = await createCliConfigStore();
|
|
42969
|
+
const config2 = await store.load();
|
|
42970
|
+
const entries = Object.entries(config2.agents);
|
|
42971
|
+
if (entries.length === 0) {
|
|
42972
|
+
print("还没有 Agent。");
|
|
42973
|
+
return 0;
|
|
42974
|
+
}
|
|
42975
|
+
print("Agent 列表:");
|
|
42976
|
+
for (const [name, agent] of entries) {
|
|
42977
|
+
const command = agent.command ? ` command=${agent.command}` : "";
|
|
42978
|
+
print(`- ${name}: driver=${agent.driver}${command}`);
|
|
42979
|
+
}
|
|
42980
|
+
return 0;
|
|
42981
|
+
}
|
|
42982
|
+
function agentTemplates(print) {
|
|
42983
|
+
print("可用 Agent 模板:");
|
|
42984
|
+
for (const name of listAgentTemplates()) {
|
|
42985
|
+
print(`- ${name}`);
|
|
42986
|
+
}
|
|
42987
|
+
return 0;
|
|
42988
|
+
}
|
|
42989
|
+
async function agentAdd(rawName, print) {
|
|
42990
|
+
const name = rawName.trim();
|
|
42991
|
+
if (name.length === 0) {
|
|
42992
|
+
print("Agent 名称不能为空。");
|
|
42993
|
+
return 1;
|
|
42994
|
+
}
|
|
42995
|
+
const template = getAgentTemplate(name);
|
|
42996
|
+
if (!template) {
|
|
42997
|
+
print(`暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}`);
|
|
42998
|
+
return 1;
|
|
42999
|
+
}
|
|
43000
|
+
const store = await createCliConfigStore();
|
|
43001
|
+
const config2 = await store.load();
|
|
43002
|
+
const existing = config2.agents[name];
|
|
43003
|
+
if (existing) {
|
|
43004
|
+
if (sameAgentConfig(existing, template)) {
|
|
43005
|
+
print(`Agent「${name}」已存在`);
|
|
43006
|
+
return 0;
|
|
43007
|
+
}
|
|
43008
|
+
print(`Agent「${name}」已存在且配置不同。请先执行:weacpx agent rm ${name}`);
|
|
43009
|
+
return 1;
|
|
43010
|
+
}
|
|
43011
|
+
await store.upsertAgent(name, template);
|
|
43012
|
+
print(`Agent「${name}」已保存`);
|
|
43013
|
+
return 0;
|
|
43014
|
+
}
|
|
43015
|
+
async function agentRemove(rawName, print) {
|
|
43016
|
+
const name = rawName.trim();
|
|
43017
|
+
if (name.length === 0) {
|
|
43018
|
+
print("Agent 名称不能为空。");
|
|
43019
|
+
return 1;
|
|
43020
|
+
}
|
|
43021
|
+
const store = await createCliConfigStore();
|
|
43022
|
+
const config2 = await store.load();
|
|
43023
|
+
if (!config2.agents[name]) {
|
|
43024
|
+
print(`没有找到 Agent「${name}」。`);
|
|
43025
|
+
return 1;
|
|
43026
|
+
}
|
|
43027
|
+
await store.removeAgent(name);
|
|
43028
|
+
print(`Agent「${name}」已删除`);
|
|
43029
|
+
return 0;
|
|
43030
|
+
}
|
|
41721
43031
|
async function createCliConfigStore() {
|
|
41722
43032
|
const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
41723
43033
|
await ensureConfigExists(configPath);
|
|
@@ -41741,6 +43051,7 @@ async function defaultLoadConfiguredPluginsForChannelCli() {
|
|
|
41741
43051
|
const { loadConfiguredPlugins: loadConfiguredPlugins2 } = await Promise.resolve().then(() => (init_plugin_loader(), exports_plugin_loader));
|
|
41742
43052
|
await loadConfiguredPlugins2({ plugins: config2.plugins });
|
|
41743
43053
|
}
|
|
43054
|
+
var DAEMON_RUN_ENV = "WEACPX_DAEMON_RUN";
|
|
41744
43055
|
async function defaultRun(options = {}) {
|
|
41745
43056
|
const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2, prepareChannelMedia: prepareChannelMedia2 }, { runConsole: runConsole2 }] = await Promise.all([
|
|
41746
43057
|
init_main().then(() => exports_main),
|
|
@@ -41769,6 +43080,7 @@ async function defaultRun(options = {}) {
|
|
|
41769
43080
|
await createFirstRunSession(runtime, firstRunOnboarding);
|
|
41770
43081
|
} : undefined,
|
|
41771
43082
|
channels: channelRegistry,
|
|
43083
|
+
channelStartupPolicy: process.env[DAEMON_RUN_ENV] === "1" ? "best-effort" : "require-one",
|
|
41772
43084
|
daemonRuntime,
|
|
41773
43085
|
...firstLockCreator ? {
|
|
41774
43086
|
consumerLockFactory: (runtime) => firstLockCreator.create({
|
|
@@ -42063,7 +43375,7 @@ function safeDaemonLogPaths() {
|
|
|
42063
43375
|
const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
42064
43376
|
const paths = resolveDaemonPaths({ home: requireHome2() });
|
|
42065
43377
|
return {
|
|
42066
|
-
appLog: join16(
|
|
43378
|
+
appLog: join16(dirname15(configPath), "runtime", "app.log"),
|
|
42067
43379
|
stderrLog: paths.stderrLog
|
|
42068
43380
|
};
|
|
42069
43381
|
} catch {
|