weacpx 0.4.10 → 0.5.1
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 +33 -0
- package/config.example.json +3 -7
- package/dist/bridge/bridge-main.js +1 -1
- package/dist/channels/types.d.ts +23 -0
- package/dist/channels/weixin-channel.d.ts +3 -1
- package/dist/cli.js +1549 -131
- package/dist/config/types.d.ts +5 -0
- package/dist/orchestration/orchestration-types.d.ts +7 -0
- package/dist/plugin-api.d.ts +1 -1
- package/dist/plugin-api.js +1 -1
- package/dist/plugins/compatibility.d.ts +1 -1
- package/dist/weixin/agent/interface.d.ts +5 -1
- package/dist/weixin/messaging/scheduled-turn.d.ts +22 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2317,6 +2317,7 @@ function parseConfig(raw, options = {}) {
|
|
|
2317
2317
|
const channelConfig = parseChannelConfig(channel, legacyWechat);
|
|
2318
2318
|
const channelsConfig = parseRuntimeChannels(raw.channels, channelConfig);
|
|
2319
2319
|
const orchestrationConfig = parseOrchestrationConfig(orchestration);
|
|
2320
|
+
const laterConfig = parseLaterConfig(raw.later);
|
|
2320
2321
|
const plugins = parsePlugins(raw.plugins);
|
|
2321
2322
|
return {
|
|
2322
2323
|
transport: {
|
|
@@ -2348,7 +2349,8 @@ function parseConfig(raw, options = {}) {
|
|
|
2348
2349
|
plugins,
|
|
2349
2350
|
agents,
|
|
2350
2351
|
workspaces,
|
|
2351
|
-
orchestration: orchestrationConfig
|
|
2352
|
+
orchestration: orchestrationConfig,
|
|
2353
|
+
later: laterConfig
|
|
2352
2354
|
};
|
|
2353
2355
|
}
|
|
2354
2356
|
function parsePluginConfig(raw, index) {
|
|
@@ -2437,6 +2439,11 @@ function parseRuntimeChannels(rawChannels, channel) {
|
|
|
2437
2439
|
}
|
|
2438
2440
|
];
|
|
2439
2441
|
}
|
|
2442
|
+
function parseLaterConfig(raw) {
|
|
2443
|
+
if (!isRecord(raw))
|
|
2444
|
+
return { ...DEFAULT_LATER_CONFIG };
|
|
2445
|
+
return raw.defaultMode === "bind" ? { defaultMode: "bind" } : { ...DEFAULT_LATER_CONFIG };
|
|
2446
|
+
}
|
|
2440
2447
|
function parseOrchestrationConfig(raw) {
|
|
2441
2448
|
if (!isRecord(raw)) {
|
|
2442
2449
|
return {
|
|
@@ -2452,7 +2459,7 @@ function parseOrchestrationConfig(raw) {
|
|
|
2452
2459
|
maxParallelTasksPerAgent: typeof raw.maxParallelTasksPerAgent === "number" && Number.isFinite(raw.maxParallelTasksPerAgent) && raw.maxParallelTasksPerAgent >= 1 ? Math.floor(raw.maxParallelTasksPerAgent) : DEFAULT_ORCHESTRATION_CONFIG.maxParallelTasksPerAgent
|
|
2453
2460
|
};
|
|
2454
2461
|
}
|
|
2455
|
-
var DEFAULT_PERF_LOG_CONFIG, DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_QUEUE_OWNER_TTL_SECONDS = 1800, DEFAULT_CHANNEL_CONFIG, DEFAULT_ORCHESTRATION_CONFIG;
|
|
2462
|
+
var DEFAULT_PERF_LOG_CONFIG, DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_QUEUE_OWNER_TTL_SECONDS = 1800, DEFAULT_CHANNEL_CONFIG, DEFAULT_ORCHESTRATION_CONFIG, DEFAULT_LATER_CONFIG;
|
|
2456
2463
|
var init_load_config = __esm(() => {
|
|
2457
2464
|
init_workspace_path();
|
|
2458
2465
|
DEFAULT_PERF_LOG_CONFIG = {
|
|
@@ -2480,6 +2487,9 @@ var init_load_config = __esm(() => {
|
|
|
2480
2487
|
progressHeartbeatSeconds: 300,
|
|
2481
2488
|
maxParallelTasksPerAgent: 3
|
|
2482
2489
|
};
|
|
2490
|
+
DEFAULT_LATER_CONFIG = {
|
|
2491
|
+
defaultMode: "temp"
|
|
2492
|
+
};
|
|
2483
2493
|
});
|
|
2484
2494
|
|
|
2485
2495
|
// src/config/config-store.ts
|
|
@@ -2547,6 +2557,15 @@ var init_config_store = __esm(() => {
|
|
|
2547
2557
|
init_load_config();
|
|
2548
2558
|
});
|
|
2549
2559
|
|
|
2560
|
+
// src/config/default-workspace.ts
|
|
2561
|
+
var DEFAULT_HOME_WORKSPACE_NAME = "home", DEFAULT_HOME_WORKSPACE;
|
|
2562
|
+
var init_default_workspace = __esm(() => {
|
|
2563
|
+
DEFAULT_HOME_WORKSPACE = {
|
|
2564
|
+
cwd: "~",
|
|
2565
|
+
description: "用户主目录"
|
|
2566
|
+
};
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2550
2569
|
// src/config/ensure-config.ts
|
|
2551
2570
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
2552
2571
|
async function ensureConfigExists(path2, options = {}) {
|
|
@@ -2601,7 +2620,9 @@ function normalizeDefaultConfigTemplate(raw) {
|
|
|
2601
2620
|
...resolveAgentCommand(agent.driver, agent.command) ? { command: resolveAgentCommand(agent.driver, agent.command) } : {}
|
|
2602
2621
|
}
|
|
2603
2622
|
])),
|
|
2604
|
-
workspaces: {
|
|
2623
|
+
workspaces: {
|
|
2624
|
+
[DEFAULT_HOME_WORKSPACE_NAME]: { ...DEFAULT_HOME_WORKSPACE }
|
|
2625
|
+
}
|
|
2605
2626
|
};
|
|
2606
2627
|
}
|
|
2607
2628
|
function isMissingFileError(error) {
|
|
@@ -2610,6 +2631,7 @@ function isMissingFileError(error) {
|
|
|
2610
2631
|
var BUILTIN_DEFAULT_CONFIG_TEMPLATE;
|
|
2611
2632
|
var init_ensure_config = __esm(() => {
|
|
2612
2633
|
init_config_store();
|
|
2634
|
+
init_default_workspace();
|
|
2613
2635
|
init_load_config();
|
|
2614
2636
|
BUILTIN_DEFAULT_CONFIG_TEMPLATE = {
|
|
2615
2637
|
transport: {
|
|
@@ -2638,7 +2660,9 @@ var init_ensure_config = __esm(() => {
|
|
|
2638
2660
|
codex: { driver: "codex" },
|
|
2639
2661
|
claude: { driver: "claude" }
|
|
2640
2662
|
},
|
|
2641
|
-
workspaces: {
|
|
2663
|
+
workspaces: {
|
|
2664
|
+
[DEFAULT_HOME_WORKSPACE_NAME]: { ...DEFAULT_HOME_WORKSPACE }
|
|
2665
|
+
},
|
|
2642
2666
|
plugins: []
|
|
2643
2667
|
};
|
|
2644
2668
|
});
|
|
@@ -9735,7 +9759,8 @@ function createEmptyState() {
|
|
|
9735
9759
|
return {
|
|
9736
9760
|
sessions: {},
|
|
9737
9761
|
chat_contexts: {},
|
|
9738
|
-
orchestration: createEmptyOrchestrationState()
|
|
9762
|
+
orchestration: createEmptyOrchestrationState(),
|
|
9763
|
+
scheduled_tasks: {}
|
|
9739
9764
|
};
|
|
9740
9765
|
}
|
|
9741
9766
|
var init_types = () => {};
|
|
@@ -9831,7 +9856,7 @@ function isCoordinatorRouteContextRecord(value) {
|
|
|
9831
9856
|
if (!isRecord2(value)) {
|
|
9832
9857
|
return false;
|
|
9833
9858
|
}
|
|
9834
|
-
return isString(value.coordinatorSession) && isString(value.chatKey) && isOptionalString(value.accountId) && isOptionalString(value.replyContextToken) && isString(value.updatedAt);
|
|
9859
|
+
return isString(value.coordinatorSession) && isString(value.chatKey) && isOptionalString(value.sessionAlias) && isOptionalString(value.accountId) && isOptionalString(value.replyContextToken) && isOptionalString(value.channel) && (value.chatType === undefined || value.chatType === "direct" || value.chatType === "group") && isOptionalString(value.senderId) && isOptionalString(value.senderName) && isOptionalString(value.groupId) && isOptionalBoolean(value.isOwner) && isString(value.updatedAt);
|
|
9835
9860
|
}
|
|
9836
9861
|
function isHumanQuestionPackageMessageRecord(value) {
|
|
9837
9862
|
if (!isRecord2(value)) {
|
|
@@ -9981,6 +10006,32 @@ function parseChatContexts(raw, path3) {
|
|
|
9981
10006
|
}
|
|
9982
10007
|
return chatContexts;
|
|
9983
10008
|
}
|
|
10009
|
+
function isScheduledTaskStatus(value) {
|
|
10010
|
+
return value === "pending" || value === "triggering" || value === "executed" || value === "cancelled" || value === "missed" || value === "failed";
|
|
10011
|
+
}
|
|
10012
|
+
function isOptionalScheduledSessionMode(value) {
|
|
10013
|
+
return value === undefined || value === "temp" || value === "bound";
|
|
10014
|
+
}
|
|
10015
|
+
function isScheduledTaskRecord(value) {
|
|
10016
|
+
if (!isRecord2(value))
|
|
10017
|
+
return false;
|
|
10018
|
+
return isString(value.id) && isString(value.chat_key) && isString(value.session_alias) && isString(value.execute_at) && isString(value.message) && isScheduledTaskStatus(value.status) && isString(value.created_at) && isOptionalString(value.account_id) && isOptionalString(value.reply_context_token) && isOptionalString(value.source_label) && isOptionalString(value.triggered_at) && isOptionalString(value.executed_at) && isOptionalString(value.cancelled_at) && isOptionalString(value.missed_at) && isOptionalString(value.failed_at) && isOptionalString(value.last_error) && isOptionalScheduledSessionMode(value.session_mode) && isOptionalString(value.agent) && isOptionalString(value.workspace);
|
|
10019
|
+
}
|
|
10020
|
+
function parseScheduledTasks(raw, path3) {
|
|
10021
|
+
if (raw === undefined)
|
|
10022
|
+
return {};
|
|
10023
|
+
if (!isRecord2(raw)) {
|
|
10024
|
+
throw new Error(`state file "${path3}" must contain an object field "scheduled_tasks"`);
|
|
10025
|
+
}
|
|
10026
|
+
const tasks = {};
|
|
10027
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
10028
|
+
if (!isScheduledTaskRecord(value) || value.id !== id) {
|
|
10029
|
+
throw new Error(`state file "${path3}" contains malformed scheduled task record "${id}"`);
|
|
10030
|
+
}
|
|
10031
|
+
tasks[id] = value;
|
|
10032
|
+
}
|
|
10033
|
+
return tasks;
|
|
10034
|
+
}
|
|
9984
10035
|
function parseState(raw, path3) {
|
|
9985
10036
|
if (!isRecord2(raw)) {
|
|
9986
10037
|
throw new Error(`state file "${path3}" must contain a JSON object`);
|
|
@@ -9999,7 +10050,8 @@ function parseState(raw, path3) {
|
|
|
9999
10050
|
return {
|
|
10000
10051
|
sessions: parsedSessions,
|
|
10001
10052
|
chat_contexts: parseChatContexts(chatContexts, path3),
|
|
10002
|
-
orchestration
|
|
10053
|
+
orchestration,
|
|
10054
|
+
scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, path3)
|
|
10003
10055
|
};
|
|
10004
10056
|
}
|
|
10005
10057
|
function validateExternalCoordinatorIdentityCollisions(sessions, orchestration, path3) {
|
|
@@ -10055,6 +10107,315 @@ var init_state_store = __esm(() => {
|
|
|
10055
10107
|
init_types();
|
|
10056
10108
|
});
|
|
10057
10109
|
|
|
10110
|
+
// src/channels/channel-scope.ts
|
|
10111
|
+
function registerKnownChannelId(channelId) {
|
|
10112
|
+
const normalized = channelId.trim();
|
|
10113
|
+
if (!normalized || normalized.includes(":")) {
|
|
10114
|
+
throw new Error("channel id must be non-empty and must not contain ':'");
|
|
10115
|
+
}
|
|
10116
|
+
KNOWN_CHANNEL_IDS.add(normalized);
|
|
10117
|
+
}
|
|
10118
|
+
function listKnownChannelIds() {
|
|
10119
|
+
return Array.from(KNOWN_CHANNEL_IDS);
|
|
10120
|
+
}
|
|
10121
|
+
function getChannelIdFromChatKey(chatKey) {
|
|
10122
|
+
const first = chatKey.split(":", 1)[0];
|
|
10123
|
+
return first && KNOWN_CHANNEL_IDS.has(first) ? first : "weixin";
|
|
10124
|
+
}
|
|
10125
|
+
function toInternalSessionAlias(channelId, displayAlias) {
|
|
10126
|
+
const normalized = displayAlias.trim();
|
|
10127
|
+
if (normalized.length === 0) {
|
|
10128
|
+
throw new Error("display session alias must be non-empty");
|
|
10129
|
+
}
|
|
10130
|
+
if (normalized.startsWith(`${channelId}:`)) {
|
|
10131
|
+
return normalized;
|
|
10132
|
+
}
|
|
10133
|
+
return `${channelId}:${normalized}`;
|
|
10134
|
+
}
|
|
10135
|
+
function toDisplaySessionAlias(internalAlias) {
|
|
10136
|
+
const [first, ...rest] = internalAlias.split(":");
|
|
10137
|
+
if (first && KNOWN_CHANNEL_IDS.has(first) && rest.length > 0) {
|
|
10138
|
+
return rest.join(":");
|
|
10139
|
+
}
|
|
10140
|
+
return internalAlias;
|
|
10141
|
+
}
|
|
10142
|
+
function isSessionAliasVisibleInChannel(alias, channelId) {
|
|
10143
|
+
const [first] = alias.split(":", 1);
|
|
10144
|
+
if (first && KNOWN_CHANNEL_IDS.has(first)) {
|
|
10145
|
+
return first === channelId;
|
|
10146
|
+
}
|
|
10147
|
+
return channelId === "weixin";
|
|
10148
|
+
}
|
|
10149
|
+
function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
|
|
10150
|
+
const normalized = displayAlias.trim();
|
|
10151
|
+
if (normalized.length === 0) {
|
|
10152
|
+
throw new Error("display session alias must be non-empty");
|
|
10153
|
+
}
|
|
10154
|
+
if (normalized.startsWith(`${channelId}:`)) {
|
|
10155
|
+
return normalized;
|
|
10156
|
+
}
|
|
10157
|
+
const scopedAlias = toInternalSessionAlias(channelId, normalized);
|
|
10158
|
+
for (const alias of existingAliases) {
|
|
10159
|
+
if (alias === scopedAlias)
|
|
10160
|
+
return scopedAlias;
|
|
10161
|
+
}
|
|
10162
|
+
if (channelId === "weixin") {
|
|
10163
|
+
for (const alias of existingAliases) {
|
|
10164
|
+
if (alias === normalized)
|
|
10165
|
+
return alias;
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
return scopedAlias;
|
|
10169
|
+
}
|
|
10170
|
+
function buildDefaultTransportSession(channelId, displayAlias) {
|
|
10171
|
+
const normalized = displayAlias.trim();
|
|
10172
|
+
if (normalized.length === 0) {
|
|
10173
|
+
throw new Error("display session alias must be non-empty");
|
|
10174
|
+
}
|
|
10175
|
+
return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
|
|
10176
|
+
}
|
|
10177
|
+
var KNOWN_CHANNEL_IDS;
|
|
10178
|
+
var init_channel_scope = __esm(() => {
|
|
10179
|
+
KNOWN_CHANNEL_IDS = new Set(["weixin"]);
|
|
10180
|
+
});
|
|
10181
|
+
|
|
10182
|
+
// src/scheduled/scheduled-types.ts
|
|
10183
|
+
var LATER_MIN_DELAY_MS = 1e4, LATER_MAX_DELAY_MS, LATER_MESSAGE_PREVIEW_CHARS = 120;
|
|
10184
|
+
var init_scheduled_types = __esm(() => {
|
|
10185
|
+
LATER_MAX_DELAY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
10186
|
+
});
|
|
10187
|
+
|
|
10188
|
+
// src/scheduled/scheduled-render.ts
|
|
10189
|
+
function sessionLabel(task, displaySession) {
|
|
10190
|
+
if (task.session_mode === "temp") {
|
|
10191
|
+
return `临时会话(${task.workspace ?? "?"} · ${task.agent ?? "?"})`;
|
|
10192
|
+
}
|
|
10193
|
+
return `会话:${displaySession}`;
|
|
10194
|
+
}
|
|
10195
|
+
function renderLaterHelp() {
|
|
10196
|
+
return [
|
|
10197
|
+
"定时任务用法:",
|
|
10198
|
+
"",
|
|
10199
|
+
"创建:",
|
|
10200
|
+
"/lt in 2h 检查 CI",
|
|
10201
|
+
"/lt 30分钟后 总结进展",
|
|
10202
|
+
"/lt tomorrow 09:00 看 PR",
|
|
10203
|
+
"/lt 周五 09:00 继续处理",
|
|
10204
|
+
"",
|
|
10205
|
+
"查看:",
|
|
10206
|
+
"/lt list",
|
|
10207
|
+
"",
|
|
10208
|
+
"取消:",
|
|
10209
|
+
"/lt cancel <id>",
|
|
10210
|
+
"",
|
|
10211
|
+
"说明:",
|
|
10212
|
+
"- 只支持一次性任务",
|
|
10213
|
+
"- 时间必须在 10 秒之后、7 天之内",
|
|
10214
|
+
"- 默认在为本次任务新建的临时会话里执行(跑完即销毁)",
|
|
10215
|
+
"- 加 --bind 改为发送到创建时绑定的当前会话",
|
|
10216
|
+
"- 触发通知和 agent 回复复用现有频道路由;微信回复额度由现有路由控制",
|
|
10217
|
+
"- 不支持延迟执行 / 开头的 weacpx 命令",
|
|
10218
|
+
"- 完整时间格式与说明见 docs/later-command.md"
|
|
10219
|
+
].join(`
|
|
10220
|
+
`);
|
|
10221
|
+
}
|
|
10222
|
+
function renderLaterUnsupportedChannel() {
|
|
10223
|
+
return [
|
|
10224
|
+
"当前频道暂不支持定时任务,未创建任务。",
|
|
10225
|
+
"",
|
|
10226
|
+
"原因:这个频道还没有实现定时消息投递能力,任务到点后无法把结果发回原聊天。",
|
|
10227
|
+
"请切换到支持定时任务的频道后再使用 /lt。"
|
|
10228
|
+
].join(`
|
|
10229
|
+
`);
|
|
10230
|
+
}
|
|
10231
|
+
function renderTaskCreated(task, displaySession) {
|
|
10232
|
+
return [
|
|
10233
|
+
`已创建定时任务 #${task.id}`,
|
|
10234
|
+
`执行时间:${formatLocalDateTime(new Date(task.execute_at))}`,
|
|
10235
|
+
sessionLabel(task, displaySession),
|
|
10236
|
+
`内容:${preview(task.message)}`
|
|
10237
|
+
].join(`
|
|
10238
|
+
`);
|
|
10239
|
+
}
|
|
10240
|
+
function renderLaterList(tasks, displaySession) {
|
|
10241
|
+
if (tasks.length === 0)
|
|
10242
|
+
return "当前没有待执行定时任务。";
|
|
10243
|
+
return [
|
|
10244
|
+
"待执行定时任务:",
|
|
10245
|
+
"",
|
|
10246
|
+
...tasks.flatMap((task) => [
|
|
10247
|
+
`#${task.id} ${formatLocalDateTime(new Date(task.execute_at))} ${sessionLabel(task, displaySession(task.session_alias))}`,
|
|
10248
|
+
preview(task.message),
|
|
10249
|
+
""
|
|
10250
|
+
])
|
|
10251
|
+
].join(`
|
|
10252
|
+
`).trimEnd();
|
|
10253
|
+
}
|
|
10254
|
+
function preview(text) {
|
|
10255
|
+
return text.length <= LATER_MESSAGE_PREVIEW_CHARS ? text : `${text.slice(0, LATER_MESSAGE_PREVIEW_CHARS - 1)}…`;
|
|
10256
|
+
}
|
|
10257
|
+
function formatLocalDateTime(date4) {
|
|
10258
|
+
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
|
10259
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
10260
|
+
return `${date4.getFullYear()}-${pad(date4.getMonth() + 1)}-${pad(date4.getDate())} ${weekdays[date4.getDay()]} ${pad(date4.getHours())}:${pad(date4.getMinutes())}`;
|
|
10261
|
+
}
|
|
10262
|
+
var init_scheduled_render = __esm(() => {
|
|
10263
|
+
init_scheduled_types();
|
|
10264
|
+
});
|
|
10265
|
+
|
|
10266
|
+
// src/orchestration/async-mutex.ts
|
|
10267
|
+
class AsyncMutex {
|
|
10268
|
+
tail = Promise.resolve();
|
|
10269
|
+
async run(critical) {
|
|
10270
|
+
const previous = this.tail;
|
|
10271
|
+
let release;
|
|
10272
|
+
this.tail = new Promise((resolve) => {
|
|
10273
|
+
release = resolve;
|
|
10274
|
+
});
|
|
10275
|
+
await previous;
|
|
10276
|
+
try {
|
|
10277
|
+
return await critical();
|
|
10278
|
+
} finally {
|
|
10279
|
+
release();
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
}
|
|
10283
|
+
|
|
10284
|
+
// src/scheduled/scheduled-service.ts
|
|
10285
|
+
class ScheduledTaskService {
|
|
10286
|
+
state;
|
|
10287
|
+
stateStore;
|
|
10288
|
+
now;
|
|
10289
|
+
generateId;
|
|
10290
|
+
stateMutex;
|
|
10291
|
+
claimedInThisSession = new Set;
|
|
10292
|
+
constructor(state, stateStore, options) {
|
|
10293
|
+
this.state = state;
|
|
10294
|
+
this.stateStore = stateStore;
|
|
10295
|
+
this.now = options?.now ?? (() => new Date);
|
|
10296
|
+
this.generateId = options?.generateId ?? (() => Math.random().toString(36).slice(2, 6));
|
|
10297
|
+
this.stateMutex = options?.stateMutex ?? new AsyncMutex;
|
|
10298
|
+
}
|
|
10299
|
+
async createTask(input) {
|
|
10300
|
+
return await this.mutate(async () => {
|
|
10301
|
+
const id = this.nextId();
|
|
10302
|
+
const task = {
|
|
10303
|
+
id,
|
|
10304
|
+
chat_key: input.chatKey,
|
|
10305
|
+
session_alias: input.sessionAlias,
|
|
10306
|
+
execute_at: input.executeAt.toISOString(),
|
|
10307
|
+
message: input.message,
|
|
10308
|
+
status: "pending",
|
|
10309
|
+
created_at: this.now().toISOString(),
|
|
10310
|
+
...input.sessionMode ? { session_mode: input.sessionMode } : {},
|
|
10311
|
+
...input.agent ? { agent: input.agent } : {},
|
|
10312
|
+
...input.workspace ? { workspace: input.workspace } : {},
|
|
10313
|
+
...input.accountId ? { account_id: input.accountId } : {},
|
|
10314
|
+
...input.replyContextToken ? { reply_context_token: input.replyContextToken } : {},
|
|
10315
|
+
...input.sourceLabel ? { source_label: input.sourceLabel } : {}
|
|
10316
|
+
};
|
|
10317
|
+
this.state.scheduled_tasks[id] = task;
|
|
10318
|
+
await this.save();
|
|
10319
|
+
return task;
|
|
10320
|
+
});
|
|
10321
|
+
}
|
|
10322
|
+
listPending() {
|
|
10323
|
+
return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
|
|
10324
|
+
}
|
|
10325
|
+
async cancelPending(inputId) {
|
|
10326
|
+
return await this.mutate(async () => {
|
|
10327
|
+
const id = normalizeId(inputId);
|
|
10328
|
+
const task = this.state.scheduled_tasks[id];
|
|
10329
|
+
if (!task || task.status !== "pending")
|
|
10330
|
+
return false;
|
|
10331
|
+
task.status = "cancelled";
|
|
10332
|
+
task.cancelled_at = this.now().toISOString();
|
|
10333
|
+
await this.save();
|
|
10334
|
+
return true;
|
|
10335
|
+
});
|
|
10336
|
+
}
|
|
10337
|
+
async markStartupMissed() {
|
|
10338
|
+
await this.mutate(async () => {
|
|
10339
|
+
const nowMs = this.now().getTime();
|
|
10340
|
+
let changed = false;
|
|
10341
|
+
for (const task of Object.values(this.state.scheduled_tasks)) {
|
|
10342
|
+
if (task.status === "pending" && Date.parse(task.execute_at) < nowMs) {
|
|
10343
|
+
task.status = "missed";
|
|
10344
|
+
task.missed_at = this.now().toISOString();
|
|
10345
|
+
changed = true;
|
|
10346
|
+
}
|
|
10347
|
+
if (task.status === "triggering" && !this.claimedInThisSession.has(task.id)) {
|
|
10348
|
+
task.status = "failed";
|
|
10349
|
+
task.failed_at = this.now().toISOString();
|
|
10350
|
+
task.last_error = "process stopped while task was triggering";
|
|
10351
|
+
changed = true;
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
if (changed)
|
|
10355
|
+
await this.save();
|
|
10356
|
+
});
|
|
10357
|
+
}
|
|
10358
|
+
async claimDueTasks() {
|
|
10359
|
+
return await this.mutate(async () => {
|
|
10360
|
+
const nowMs = this.now().getTime();
|
|
10361
|
+
const due = this.listPending().filter((task) => Date.parse(task.execute_at) <= nowMs);
|
|
10362
|
+
if (due.length === 0)
|
|
10363
|
+
return [];
|
|
10364
|
+
const at = this.now().toISOString();
|
|
10365
|
+
for (const task of due) {
|
|
10366
|
+
task.status = "triggering";
|
|
10367
|
+
task.triggered_at = at;
|
|
10368
|
+
this.claimedInThisSession.add(task.id);
|
|
10369
|
+
}
|
|
10370
|
+
await this.save();
|
|
10371
|
+
return due.map((task) => ({ ...task }));
|
|
10372
|
+
});
|
|
10373
|
+
}
|
|
10374
|
+
async markExecuted(id) {
|
|
10375
|
+
await this.mutate(async () => {
|
|
10376
|
+
const taskId = normalizeId(id);
|
|
10377
|
+
const task = this.state.scheduled_tasks[taskId];
|
|
10378
|
+
if (!task)
|
|
10379
|
+
return;
|
|
10380
|
+
task.status = "executed";
|
|
10381
|
+
task.executed_at = this.now().toISOString();
|
|
10382
|
+
this.claimedInThisSession.delete(taskId);
|
|
10383
|
+
await this.save();
|
|
10384
|
+
});
|
|
10385
|
+
}
|
|
10386
|
+
async markFailed(id, error2) {
|
|
10387
|
+
await this.mutate(async () => {
|
|
10388
|
+
const taskId = normalizeId(id);
|
|
10389
|
+
const task = this.state.scheduled_tasks[taskId];
|
|
10390
|
+
if (!task)
|
|
10391
|
+
return;
|
|
10392
|
+
task.status = "failed";
|
|
10393
|
+
task.failed_at = this.now().toISOString();
|
|
10394
|
+
task.last_error = error2 instanceof Error ? error2.message : String(error2);
|
|
10395
|
+
this.claimedInThisSession.delete(taskId);
|
|
10396
|
+
await this.save();
|
|
10397
|
+
});
|
|
10398
|
+
}
|
|
10399
|
+
nextId() {
|
|
10400
|
+
for (let attempt = 0;attempt < 20; attempt += 1) {
|
|
10401
|
+
const id = normalizeId(this.generateId()).replace(/[^0-9a-z]/g, "").slice(0, 6);
|
|
10402
|
+
if (id.length >= 4 && !this.state.scheduled_tasks[id])
|
|
10403
|
+
return id;
|
|
10404
|
+
}
|
|
10405
|
+
throw new Error("failed to generate unique scheduled task id");
|
|
10406
|
+
}
|
|
10407
|
+
async mutate(critical) {
|
|
10408
|
+
return await this.stateMutex.run(critical);
|
|
10409
|
+
}
|
|
10410
|
+
async save() {
|
|
10411
|
+
await this.stateStore.save(this.state);
|
|
10412
|
+
}
|
|
10413
|
+
}
|
|
10414
|
+
function normalizeId(input) {
|
|
10415
|
+
return input.trim().replace(/^#/, "").toLowerCase();
|
|
10416
|
+
}
|
|
10417
|
+
var init_scheduled_service = () => {};
|
|
10418
|
+
|
|
10058
10419
|
// src/plugins/plugin-home.ts
|
|
10059
10420
|
import { mkdir as mkdir6, writeFile as writeFile4 } from "node:fs/promises";
|
|
10060
10421
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -15442,6 +15803,181 @@ var init_deliver_coordinator_message = __esm(() => {
|
|
|
15442
15803
|
init_send();
|
|
15443
15804
|
});
|
|
15444
15805
|
|
|
15806
|
+
// src/weixin/messaging/scheduled-turn.ts
|
|
15807
|
+
async function executeScheduledTurn(input, deps) {
|
|
15808
|
+
const userId = normalizeWeixinUserIdFromChatKey(input.chatKey);
|
|
15809
|
+
const quotaKey = userId;
|
|
15810
|
+
const sendMessage2 = deps.sendMessage ?? sendMessageWeixin;
|
|
15811
|
+
const candidateAccountIds = input.accountId ? [input.accountId] : deps.listAccountIds();
|
|
15812
|
+
if (candidateAccountIds.length === 0) {
|
|
15813
|
+
throw new Error(`no weixin account is available for scheduled message on chatKey: ${input.chatKey}`);
|
|
15814
|
+
}
|
|
15815
|
+
let noticeSent = false;
|
|
15816
|
+
let lastNoticeError;
|
|
15817
|
+
let deliveryAccountId;
|
|
15818
|
+
let deliveryContextToken;
|
|
15819
|
+
let deliverableAccountId;
|
|
15820
|
+
let deliverableContextToken;
|
|
15821
|
+
const resolveContextToken = (candidateAccountId) => deps.getContextToken(candidateAccountId, userId) ?? (candidateAccountId === input.accountId ? input.replyContextToken : undefined);
|
|
15822
|
+
for (const candidateAccountId of candidateAccountIds) {
|
|
15823
|
+
const contextToken = resolveContextToken(candidateAccountId);
|
|
15824
|
+
if (!contextToken)
|
|
15825
|
+
continue;
|
|
15826
|
+
const account = deps.resolveAccount(candidateAccountId);
|
|
15827
|
+
if (!account.token)
|
|
15828
|
+
continue;
|
|
15829
|
+
if (!deliverableAccountId) {
|
|
15830
|
+
deliverableAccountId = candidateAccountId;
|
|
15831
|
+
deliverableContextToken = contextToken;
|
|
15832
|
+
}
|
|
15833
|
+
try {
|
|
15834
|
+
if (!deps.reserveMidSegment(quotaKey)) {
|
|
15835
|
+
throw new Error("mid segment quota exhausted");
|
|
15836
|
+
}
|
|
15837
|
+
await sendMessage2({
|
|
15838
|
+
to: userId,
|
|
15839
|
+
text: input.noticeText,
|
|
15840
|
+
opts: { baseUrl: account.baseUrl, token: account.token, contextToken }
|
|
15841
|
+
});
|
|
15842
|
+
noticeSent = true;
|
|
15843
|
+
deliveryAccountId = candidateAccountId;
|
|
15844
|
+
deliveryContextToken = contextToken;
|
|
15845
|
+
break;
|
|
15846
|
+
} catch (error2) {
|
|
15847
|
+
lastNoticeError = error2;
|
|
15848
|
+
await deps.logger.error("scheduled.notice_send_failed", "failed to send scheduled notice", { chatKey: input.chatKey, accountId: candidateAccountId, error: String(error2) });
|
|
15849
|
+
}
|
|
15850
|
+
}
|
|
15851
|
+
if (!noticeSent) {
|
|
15852
|
+
if (!deliverableAccountId || !deliverableContextToken) {
|
|
15853
|
+
const message = lastNoticeError instanceof Error ? lastNoticeError.message : `no deliverable weixin context for scheduled message on chatKey: ${input.chatKey}`;
|
|
15854
|
+
throw new Error(message);
|
|
15855
|
+
}
|
|
15856
|
+
deliveryAccountId = deliverableAccountId;
|
|
15857
|
+
deliveryContextToken = deliverableContextToken;
|
|
15858
|
+
await deps.logger.info("scheduled.notice_skipped", "scheduled trigger notice was not delivered; proceeding with agent turn", {
|
|
15859
|
+
chatKey: input.chatKey,
|
|
15860
|
+
accountId: deliveryAccountId,
|
|
15861
|
+
reason: lastNoticeError instanceof Error ? lastNoticeError.message : "notice_undelivered"
|
|
15862
|
+
});
|
|
15863
|
+
}
|
|
15864
|
+
const sendReplySegment = async (text) => {
|
|
15865
|
+
const plainText = markdownToPlainText(text).trim();
|
|
15866
|
+
if (plainText.length === 0)
|
|
15867
|
+
return false;
|
|
15868
|
+
return await sendTextViaAvailableAccount(plainText, "scheduled.mid_send_failed");
|
|
15869
|
+
};
|
|
15870
|
+
const sendReservedMidText = async (text) => {
|
|
15871
|
+
const plainText = markdownToPlainText(text).trim();
|
|
15872
|
+
if (plainText.length === 0)
|
|
15873
|
+
return false;
|
|
15874
|
+
if (!deps.reserveMidSegment(quotaKey)) {
|
|
15875
|
+
await deps.logger.info("scheduled.mid_dropped", "scheduled turn intermediate response dropped due to quota", { chatKey: input.chatKey, reason: "quota_exhausted" });
|
|
15876
|
+
return false;
|
|
15877
|
+
}
|
|
15878
|
+
return await sendTextViaAvailableAccount(plainText, "scheduled.mid_send_failed");
|
|
15879
|
+
};
|
|
15880
|
+
const resolvedAccountId = deliveryAccountId ?? input.accountId ?? candidateAccountIds[0];
|
|
15881
|
+
let turn;
|
|
15882
|
+
try {
|
|
15883
|
+
turn = await executeChatTurn({
|
|
15884
|
+
agent: deps.agent,
|
|
15885
|
+
request: {
|
|
15886
|
+
accountId: resolvedAccountId,
|
|
15887
|
+
conversationId: input.chatKey,
|
|
15888
|
+
text: input.promptText,
|
|
15889
|
+
...deliveryContextToken ? { replyContextToken: deliveryContextToken } : {},
|
|
15890
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {},
|
|
15891
|
+
metadata: {
|
|
15892
|
+
channel: "weixin",
|
|
15893
|
+
scheduledSessionAlias: input.sessionAlias,
|
|
15894
|
+
...input.sessionDescriptor ? { scheduledSessionDescriptor: input.sessionDescriptor } : {}
|
|
15895
|
+
}
|
|
15896
|
+
},
|
|
15897
|
+
onReplySegment: sendReplySegment
|
|
15898
|
+
});
|
|
15899
|
+
} catch (error2) {
|
|
15900
|
+
await sendReservedMidText(`定时任务执行失败:${error2 instanceof Error ? error2.message : String(error2)}`).catch(() => false);
|
|
15901
|
+
throw error2;
|
|
15902
|
+
}
|
|
15903
|
+
if (turn.text) {
|
|
15904
|
+
const finalText = markdownToPlainText(turn.text).trim();
|
|
15905
|
+
if (finalText.length > 0) {
|
|
15906
|
+
await sendFinalText(finalText);
|
|
15907
|
+
}
|
|
15908
|
+
}
|
|
15909
|
+
async function sendFinalText(finalText) {
|
|
15910
|
+
const rawChunks = chunkFinalText(finalText, 1800);
|
|
15911
|
+
if (rawChunks.length === 0)
|
|
15912
|
+
return;
|
|
15913
|
+
const total = rawChunks.length;
|
|
15914
|
+
const chunks = total === 1 ? rawChunks : rawChunks.map((body, index) => `(${index + 1}/${total}) ${body}`);
|
|
15915
|
+
const available = total === 1 ? 1 : Math.max(Math.min(deps.finalRemaining?.(quotaKey) ?? total, total), 0);
|
|
15916
|
+
const wave = chunks.slice(0, available);
|
|
15917
|
+
if (wave.length > 0 && wave.length < total) {
|
|
15918
|
+
wave[wave.length - 1] = `${wave[wave.length - 1]}
|
|
15919
|
+
|
|
15920
|
+
${buildFinalHeadsUp({
|
|
15921
|
+
total,
|
|
15922
|
+
sentSoFar: wave.length
|
|
15923
|
+
})}`;
|
|
15924
|
+
}
|
|
15925
|
+
let sent = 0;
|
|
15926
|
+
for (let index = 0;index < wave.length; index += 1) {
|
|
15927
|
+
if (!deps.reserveFinal(quotaKey)) {
|
|
15928
|
+
await deps.logger.info("scheduled.final_dropped", "scheduled turn final response dropped due to quota", { chatKey: input.chatKey, reason: "quota_exhausted", chunk: index + 1, total });
|
|
15929
|
+
break;
|
|
15930
|
+
}
|
|
15931
|
+
const delivered = await sendTextViaAvailableAccount(wave[index], "scheduled.final_send_failed");
|
|
15932
|
+
if (!delivered)
|
|
15933
|
+
break;
|
|
15934
|
+
sent += 1;
|
|
15935
|
+
}
|
|
15936
|
+
const restToPark = chunks.slice(sent);
|
|
15937
|
+
if (total > 1 && restToPark.length > 0 && deps.enqueuePendingFinal) {
|
|
15938
|
+
const pending = restToPark.map((text, index) => {
|
|
15939
|
+
const entry = { text, seq: sent + index + 1, total };
|
|
15940
|
+
if (deliveryContextToken)
|
|
15941
|
+
entry.contextToken = deliveryContextToken;
|
|
15942
|
+
if (deliveryAccountId)
|
|
15943
|
+
entry.accountId = deliveryAccountId;
|
|
15944
|
+
return entry;
|
|
15945
|
+
});
|
|
15946
|
+
deps.enqueuePendingFinal(quotaKey, pending);
|
|
15947
|
+
}
|
|
15948
|
+
}
|
|
15949
|
+
async function sendTextViaAvailableAccount(text, errorEvent) {
|
|
15950
|
+
const orderedAccountIds = [
|
|
15951
|
+
...deliveryAccountId ? [deliveryAccountId] : [],
|
|
15952
|
+
...candidateAccountIds.filter((accountId) => accountId !== deliveryAccountId)
|
|
15953
|
+
];
|
|
15954
|
+
for (const candidateAccountId of orderedAccountIds) {
|
|
15955
|
+
const contextToken = candidateAccountId === deliveryAccountId && deliveryContextToken ? deliveryContextToken : resolveContextToken(candidateAccountId);
|
|
15956
|
+
if (!contextToken)
|
|
15957
|
+
continue;
|
|
15958
|
+
const account = deps.resolveAccount(candidateAccountId);
|
|
15959
|
+
if (!account.token)
|
|
15960
|
+
continue;
|
|
15961
|
+
try {
|
|
15962
|
+
await sendMessage2({
|
|
15963
|
+
to: userId,
|
|
15964
|
+
text,
|
|
15965
|
+
opts: { baseUrl: account.baseUrl, token: account.token, contextToken }
|
|
15966
|
+
});
|
|
15967
|
+
return true;
|
|
15968
|
+
} catch (error2) {
|
|
15969
|
+
await deps.logger.error(errorEvent, "failed to send scheduled response text", { chatKey: input.chatKey, accountId: candidateAccountId, error: String(error2) });
|
|
15970
|
+
}
|
|
15971
|
+
}
|
|
15972
|
+
return false;
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
var init_scheduled_turn = __esm(() => {
|
|
15976
|
+
init_handle_weixin_message_turn();
|
|
15977
|
+
init_send();
|
|
15978
|
+
init_inbound();
|
|
15979
|
+
});
|
|
15980
|
+
|
|
15445
15981
|
// src/weixin/monitor/consumer-lock.ts
|
|
15446
15982
|
import { mkdir as mkdir8, open as open3, readFile as readFile6, rm as rm6 } from "node:fs/promises";
|
|
15447
15983
|
import { dirname as dirname8, join as join5 } from "node:path";
|
|
@@ -15568,6 +16104,7 @@ var init_consumer_lock = __esm(() => {
|
|
|
15568
16104
|
// src/channels/weixin-channel.ts
|
|
15569
16105
|
class WeixinChannel {
|
|
15570
16106
|
id = "weixin";
|
|
16107
|
+
agent = null;
|
|
15571
16108
|
quota = null;
|
|
15572
16109
|
logger = null;
|
|
15573
16110
|
markDelivered = null;
|
|
@@ -15598,6 +16135,7 @@ class WeixinChannel {
|
|
|
15598
16135
|
this.markFailed = callbacks.markTaskNoticeFailed;
|
|
15599
16136
|
}
|
|
15600
16137
|
async start(input) {
|
|
16138
|
+
this.agent = input.agent;
|
|
15601
16139
|
this.quota = input.quota;
|
|
15602
16140
|
this.logger = input.logger;
|
|
15603
16141
|
if (!this.isLoggedIn()) {
|
|
@@ -15658,6 +16196,23 @@ class WeixinChannel {
|
|
|
15658
16196
|
logger: this.logger
|
|
15659
16197
|
});
|
|
15660
16198
|
}
|
|
16199
|
+
async sendScheduledMessage(input) {
|
|
16200
|
+
if (!this.agent || !this.quota || !this.logger) {
|
|
16201
|
+
throw new Error("WeixinChannel.start() must be called before scheduled message delivery");
|
|
16202
|
+
}
|
|
16203
|
+
await executeScheduledTurn(input, {
|
|
16204
|
+
agent: this.agent,
|
|
16205
|
+
listAccountIds: () => listWeixinAccountIds(),
|
|
16206
|
+
resolveAccount: (accountId) => resolveWeixinAccount(accountId),
|
|
16207
|
+
getContextToken: (accountId, userId) => getContextToken(accountId, userId),
|
|
16208
|
+
reserveMidSegment: (chatKey) => this.quota.reserveMidSegment(chatKey),
|
|
16209
|
+
reserveFinal: (chatKey) => this.quota.reserveFinal(chatKey),
|
|
16210
|
+
finalRemaining: (chatKey) => this.quota.finalRemaining(chatKey),
|
|
16211
|
+
enqueuePendingFinal: (chatKey, chunks) => this.quota.enqueuePendingFinal(chatKey, chunks),
|
|
16212
|
+
sendMessage: sendMessageWeixin,
|
|
16213
|
+
logger: this.logger
|
|
16214
|
+
});
|
|
16215
|
+
}
|
|
15661
16216
|
}
|
|
15662
16217
|
var init_weixin_channel = __esm(() => {
|
|
15663
16218
|
init_weixin();
|
|
@@ -15665,81 +16220,10 @@ var init_weixin_channel = __esm(() => {
|
|
|
15665
16220
|
init_deliver_orchestration_task_notice();
|
|
15666
16221
|
init_deliver_orchestration_task_progress();
|
|
15667
16222
|
init_deliver_coordinator_message();
|
|
16223
|
+
init_scheduled_turn();
|
|
15668
16224
|
init_consumer_lock();
|
|
15669
16225
|
});
|
|
15670
16226
|
|
|
15671
|
-
// src/channels/channel-scope.ts
|
|
15672
|
-
function registerKnownChannelId(channelId) {
|
|
15673
|
-
const normalized = channelId.trim();
|
|
15674
|
-
if (!normalized || normalized.includes(":")) {
|
|
15675
|
-
throw new Error("channel id must be non-empty and must not contain ':'");
|
|
15676
|
-
}
|
|
15677
|
-
KNOWN_CHANNEL_IDS.add(normalized);
|
|
15678
|
-
}
|
|
15679
|
-
function listKnownChannelIds() {
|
|
15680
|
-
return Array.from(KNOWN_CHANNEL_IDS);
|
|
15681
|
-
}
|
|
15682
|
-
function getChannelIdFromChatKey(chatKey) {
|
|
15683
|
-
const first = chatKey.split(":", 1)[0];
|
|
15684
|
-
return first && KNOWN_CHANNEL_IDS.has(first) ? first : "weixin";
|
|
15685
|
-
}
|
|
15686
|
-
function toInternalSessionAlias(channelId, displayAlias) {
|
|
15687
|
-
const normalized = displayAlias.trim();
|
|
15688
|
-
if (normalized.length === 0) {
|
|
15689
|
-
throw new Error("display session alias must be non-empty");
|
|
15690
|
-
}
|
|
15691
|
-
if (normalized.startsWith(`${channelId}:`)) {
|
|
15692
|
-
return normalized;
|
|
15693
|
-
}
|
|
15694
|
-
return `${channelId}:${normalized}`;
|
|
15695
|
-
}
|
|
15696
|
-
function toDisplaySessionAlias(internalAlias) {
|
|
15697
|
-
const [first, ...rest] = internalAlias.split(":");
|
|
15698
|
-
if (first && KNOWN_CHANNEL_IDS.has(first) && rest.length > 0) {
|
|
15699
|
-
return rest.join(":");
|
|
15700
|
-
}
|
|
15701
|
-
return internalAlias;
|
|
15702
|
-
}
|
|
15703
|
-
function isSessionAliasVisibleInChannel(alias, channelId) {
|
|
15704
|
-
const [first] = alias.split(":", 1);
|
|
15705
|
-
if (first && KNOWN_CHANNEL_IDS.has(first)) {
|
|
15706
|
-
return first === channelId;
|
|
15707
|
-
}
|
|
15708
|
-
return channelId === "weixin";
|
|
15709
|
-
}
|
|
15710
|
-
function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
|
|
15711
|
-
const normalized = displayAlias.trim();
|
|
15712
|
-
if (normalized.length === 0) {
|
|
15713
|
-
throw new Error("display session alias must be non-empty");
|
|
15714
|
-
}
|
|
15715
|
-
if (normalized.startsWith(`${channelId}:`)) {
|
|
15716
|
-
return normalized;
|
|
15717
|
-
}
|
|
15718
|
-
const scopedAlias = toInternalSessionAlias(channelId, normalized);
|
|
15719
|
-
for (const alias of existingAliases) {
|
|
15720
|
-
if (alias === scopedAlias)
|
|
15721
|
-
return scopedAlias;
|
|
15722
|
-
}
|
|
15723
|
-
if (channelId === "weixin") {
|
|
15724
|
-
for (const alias of existingAliases) {
|
|
15725
|
-
if (alias === normalized)
|
|
15726
|
-
return alias;
|
|
15727
|
-
}
|
|
15728
|
-
}
|
|
15729
|
-
return scopedAlias;
|
|
15730
|
-
}
|
|
15731
|
-
function buildDefaultTransportSession(channelId, displayAlias) {
|
|
15732
|
-
const normalized = displayAlias.trim();
|
|
15733
|
-
if (normalized.length === 0) {
|
|
15734
|
-
throw new Error("display session alias must be non-empty");
|
|
15735
|
-
}
|
|
15736
|
-
return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
|
|
15737
|
-
}
|
|
15738
|
-
var KNOWN_CHANNEL_IDS;
|
|
15739
|
-
var init_channel_scope = __esm(() => {
|
|
15740
|
-
KNOWN_CHANNEL_IDS = new Set(["weixin"]);
|
|
15741
|
-
});
|
|
15742
|
-
|
|
15743
16227
|
// src/plugins/known-plugins.ts
|
|
15744
16228
|
function listKnownPlugins() {
|
|
15745
16229
|
return KNOWN_PLUGINS.map((plugin) => ({ ...plugin, channels: [...plugin.channels] }));
|
|
@@ -16049,7 +16533,7 @@ function validatePluginCompatibility(metadata, context) {
|
|
|
16049
16533
|
}
|
|
16050
16534
|
}
|
|
16051
16535
|
}
|
|
16052
|
-
var WEACPX_PLUGIN_API_VERSION = 1, WEACPX_PLUGIN_API_SUPPORTED_VERSIONS, WEACPX_PLUGIN_MIN_CORE_VERSION = "0.
|
|
16536
|
+
var WEACPX_PLUGIN_API_VERSION = 1, WEACPX_PLUGIN_API_SUPPORTED_VERSIONS, WEACPX_PLUGIN_MIN_CORE_VERSION = "0.5.0", SEMVER_RE;
|
|
16053
16537
|
var init_compatibility = __esm(() => {
|
|
16054
16538
|
WEACPX_PLUGIN_API_SUPPORTED_VERSIONS = [1];
|
|
16055
16539
|
SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
@@ -16435,7 +16919,9 @@ var init_command_list = __esm(() => {
|
|
|
16435
16919
|
"/dg",
|
|
16436
16920
|
"/group",
|
|
16437
16921
|
"/groups",
|
|
16438
|
-
"/task"
|
|
16922
|
+
"/task",
|
|
16923
|
+
"/later",
|
|
16924
|
+
"/lt"
|
|
16439
16925
|
];
|
|
16440
16926
|
KNOWN_COMMAND_PREFIX_SET = new Set(WEACPX_KNOWN_COMMAND_PREFIXES);
|
|
16441
16927
|
});
|
|
@@ -16603,6 +17089,16 @@ function parseCommand(input) {
|
|
|
16603
17089
|
} else if (command === "/task" && parts[1] && parts.length === 2) {
|
|
16604
17090
|
return { kind: "task.get", taskId: parts[1] };
|
|
16605
17091
|
}
|
|
17092
|
+
if (command === "/later") {
|
|
17093
|
+
if (parts.length === 1)
|
|
17094
|
+
return { kind: "later.help" };
|
|
17095
|
+
if (parts[1] === "list" && parts.length === 2)
|
|
17096
|
+
return { kind: "later.list" };
|
|
17097
|
+
if (parts[1] === "cancel" && parts[2] && parts.length === 3) {
|
|
17098
|
+
return { kind: "later.cancel", id: parts[2] };
|
|
17099
|
+
}
|
|
17100
|
+
return { kind: "later.create", tokens: parts.slice(1) };
|
|
17101
|
+
}
|
|
16606
17102
|
if (command === "/workspace" && parts[1] === "new" && parts[2]) {
|
|
16607
17103
|
const name = parts[2];
|
|
16608
17104
|
let cwd = "";
|
|
@@ -16771,6 +17267,8 @@ function normalizeCommand(command) {
|
|
|
16771
17267
|
return "/permission";
|
|
16772
17268
|
if (command === "/stop")
|
|
16773
17269
|
return "/cancel";
|
|
17270
|
+
if (command === "/lt")
|
|
17271
|
+
return "/later";
|
|
16774
17272
|
return command;
|
|
16775
17273
|
}
|
|
16776
17274
|
function isRecognizedCommand(command) {
|
|
@@ -16966,6 +17464,7 @@ var init_command_policy = __esm(() => {
|
|
|
16966
17464
|
"group.get",
|
|
16967
17465
|
"tasks",
|
|
16968
17466
|
"task.get",
|
|
17467
|
+
"later.help",
|
|
16969
17468
|
"invalid",
|
|
16970
17469
|
"prompt"
|
|
16971
17470
|
]);
|
|
@@ -16995,7 +17494,10 @@ var init_command_policy = __esm(() => {
|
|
|
16995
17494
|
"session.new": "/session new",
|
|
16996
17495
|
"session.shortcut": "/session",
|
|
16997
17496
|
"session.shortcut.new": "/session",
|
|
16998
|
-
"session.attach": "/session attach"
|
|
17497
|
+
"session.attach": "/session attach",
|
|
17498
|
+
"later.create": "/later",
|
|
17499
|
+
"later.list": "/later list",
|
|
17500
|
+
"later.cancel": "/later cancel"
|
|
16999
17501
|
};
|
|
17000
17502
|
});
|
|
17001
17503
|
|
|
@@ -17904,7 +18406,7 @@ async function handleSessionRemove(context, chatKey, alias) {
|
|
|
17904
18406
|
return { text: lines.join(`
|
|
17905
18407
|
`) };
|
|
17906
18408
|
}
|
|
17907
|
-
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
18409
|
+
async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
|
|
17908
18410
|
const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
|
|
17909
18411
|
if (!session.replyMode)
|
|
17910
18412
|
session.replyMode = effectiveReplyMode;
|
|
@@ -17914,8 +18416,10 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
17914
18416
|
await context.orchestration.recordCoordinatorRouteContext?.({
|
|
17915
18417
|
coordinatorSession: session.transportSession,
|
|
17916
18418
|
chatKey,
|
|
18419
|
+
sessionAlias: session.alias,
|
|
17917
18420
|
...replyContextToken ? { replyContextToken } : {},
|
|
17918
|
-
...accountId ? { accountId } : {}
|
|
18421
|
+
...accountId ? { accountId } : {},
|
|
18422
|
+
...toCoordinatorRouteChatMetadata(metadata)
|
|
17919
18423
|
});
|
|
17920
18424
|
} catch (error2) {
|
|
17921
18425
|
await context.logger.error("orchestration.coordinator_route_context.record_failed", "failed to record coordinator route context", {
|
|
@@ -17924,6 +18428,13 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
17924
18428
|
chatKey,
|
|
17925
18429
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
17926
18430
|
});
|
|
18431
|
+
return {
|
|
18432
|
+
text: [
|
|
18433
|
+
"无法记录当前会话路由,已取消本次发送。",
|
|
18434
|
+
"请稍后重试;如果问题持续存在,请检查 weacpx 运行日志和 state.json 写入权限。"
|
|
18435
|
+
].join(`
|
|
18436
|
+
`)
|
|
18437
|
+
};
|
|
17927
18438
|
}
|
|
17928
18439
|
}
|
|
17929
18440
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
|
|
@@ -17949,21 +18460,37 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
|
|
|
17949
18460
|
throw error2;
|
|
17950
18461
|
}
|
|
17951
18462
|
}
|
|
17952
|
-
async function
|
|
17953
|
-
const session = await context.sessions.getCurrentSession(chatKey);
|
|
17954
|
-
if (!session) {
|
|
17955
|
-
return { text: NO_CURRENT_SESSION_TEXT };
|
|
17956
|
-
}
|
|
18463
|
+
async function handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
|
|
17957
18464
|
try {
|
|
17958
|
-
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
18465
|
+
return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
17959
18466
|
} catch (error2) {
|
|
17960
18467
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
|
|
17961
18468
|
if (recovered) {
|
|
17962
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
|
|
18469
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
17963
18470
|
}
|
|
17964
18471
|
return context.recovery.renderTransportError(session, error2);
|
|
17965
18472
|
}
|
|
17966
18473
|
}
|
|
18474
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
|
|
18475
|
+
const session = await context.sessions.getCurrentSession(chatKey);
|
|
18476
|
+
if (!session) {
|
|
18477
|
+
return { text: NO_CURRENT_SESSION_TEXT };
|
|
18478
|
+
}
|
|
18479
|
+
return await handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
18480
|
+
}
|
|
18481
|
+
function toCoordinatorRouteChatMetadata(metadata) {
|
|
18482
|
+
if (!metadata) {
|
|
18483
|
+
return {};
|
|
18484
|
+
}
|
|
18485
|
+
return {
|
|
18486
|
+
...metadata.channel ? { channel: metadata.channel } : {},
|
|
18487
|
+
...metadata.chatType ? { chatType: metadata.chatType } : {},
|
|
18488
|
+
...metadata.senderId ? { senderId: metadata.senderId } : {},
|
|
18489
|
+
...metadata.senderName ? { senderName: metadata.senderName } : {},
|
|
18490
|
+
...metadata.groupId ? { groupId: metadata.groupId } : {},
|
|
18491
|
+
...metadata.isOwner !== undefined ? { isOwner: metadata.isOwner } : {}
|
|
18492
|
+
};
|
|
18493
|
+
}
|
|
17967
18494
|
async function preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId) {
|
|
17968
18495
|
const orchestration = context.orchestration;
|
|
17969
18496
|
if (!orchestration) {
|
|
@@ -18725,6 +19252,273 @@ var init_workspace_handler = __esm(() => {
|
|
|
18725
19252
|
};
|
|
18726
19253
|
});
|
|
18727
19254
|
|
|
19255
|
+
// src/scheduled/parse-later-time.ts
|
|
19256
|
+
function parseLaterTime(tokens, now = new Date) {
|
|
19257
|
+
if (tokens.length === 0)
|
|
19258
|
+
return { ok: false, code: "missing_time" };
|
|
19259
|
+
const relative = parseRelative(tokens, now);
|
|
19260
|
+
if (relative)
|
|
19261
|
+
return validateResult(relative.executeAt, relative.messageStartIndex, tokens, now);
|
|
19262
|
+
const absolute = parseAbsolute(tokens, now);
|
|
19263
|
+
if (absolute)
|
|
19264
|
+
return validateResult(absolute.executeAt, absolute.messageStartIndex, tokens, now, absolute.pastTodayValue);
|
|
19265
|
+
return { ok: false, code: "unrecognized_time" };
|
|
19266
|
+
}
|
|
19267
|
+
function parseRelative(tokens, now) {
|
|
19268
|
+
if (tokens[0] === "in" && tokens[1]) {
|
|
19269
|
+
const ms = parseDuration(tokens[1]);
|
|
19270
|
+
if (ms !== null)
|
|
19271
|
+
return { executeAt: new Date(now.getTime() + ms), messageStartIndex: 2 };
|
|
19272
|
+
}
|
|
19273
|
+
const zh = /^(\d+)(分钟|小时|天)后$/.exec(tokens[0] ?? "");
|
|
19274
|
+
if (zh) {
|
|
19275
|
+
const amount = Number(zh[1]);
|
|
19276
|
+
const unit = zh[2];
|
|
19277
|
+
const ms = unit === "分钟" ? amount * 60000 : unit === "小时" ? amount * 3600000 : amount * 86400000;
|
|
19278
|
+
return { executeAt: new Date(now.getTime() + ms), messageStartIndex: 1 };
|
|
19279
|
+
}
|
|
19280
|
+
return null;
|
|
19281
|
+
}
|
|
19282
|
+
function parseDuration(value) {
|
|
19283
|
+
const match = /^(\d+)(m|min|minute|minutes|h|hour|hours|d|day|days)$/.exec(value.toLowerCase());
|
|
19284
|
+
if (!match)
|
|
19285
|
+
return null;
|
|
19286
|
+
const amount = Number(match[1]);
|
|
19287
|
+
const unit = match[2];
|
|
19288
|
+
if (unit === "m" || unit === "min" || unit === "minute" || unit === "minutes")
|
|
19289
|
+
return amount * 60000;
|
|
19290
|
+
if (unit === "h" || unit === "hour" || unit === "hours")
|
|
19291
|
+
return amount * 3600000;
|
|
19292
|
+
return amount * 86400000;
|
|
19293
|
+
}
|
|
19294
|
+
function parseAbsolute(tokens, now) {
|
|
19295
|
+
if (tokens[0] === "at" && tokens[1]) {
|
|
19296
|
+
const parsed = parseClock(tokens[1]);
|
|
19297
|
+
if (!parsed)
|
|
19298
|
+
return null;
|
|
19299
|
+
const executeAt = atLocalDate(now, 0, parsed.hour, parsed.minute);
|
|
19300
|
+
if (executeAt.getTime() <= now.getTime())
|
|
19301
|
+
return { executeAt, messageStartIndex: 2, pastTodayValue: tokens[1] };
|
|
19302
|
+
return { executeAt, messageStartIndex: 2 };
|
|
19303
|
+
}
|
|
19304
|
+
const dayWord = tokens[0]?.toLowerCase();
|
|
19305
|
+
const dayOffset = dayWord === "today" || dayWord === "今天" ? 0 : dayWord === "tomorrow" || dayWord === "明天" ? 1 : dayWord === "后天" ? 2 : null;
|
|
19306
|
+
if (dayOffset !== null && tokens[1]) {
|
|
19307
|
+
const parsed = parseClock(tokens[1]);
|
|
19308
|
+
if (!parsed)
|
|
19309
|
+
return null;
|
|
19310
|
+
const executeAt = atLocalDate(now, dayOffset, parsed.hour, parsed.minute);
|
|
19311
|
+
if (dayOffset === 0 && executeAt.getTime() <= now.getTime())
|
|
19312
|
+
return { executeAt, messageStartIndex: 2, pastTodayValue: tokens[1] };
|
|
19313
|
+
return { executeAt, messageStartIndex: 2 };
|
|
19314
|
+
}
|
|
19315
|
+
const weekday = WEEKDAYS.get(tokens[0]?.toLowerCase() ?? "");
|
|
19316
|
+
if (weekday !== undefined && tokens[1]) {
|
|
19317
|
+
const parsed = parseClock(tokens[1]);
|
|
19318
|
+
if (!parsed)
|
|
19319
|
+
return null;
|
|
19320
|
+
let days = (weekday - now.getDay() + 7) % 7;
|
|
19321
|
+
let executeAt = atLocalDate(now, days, parsed.hour, parsed.minute);
|
|
19322
|
+
if (days === 0 && executeAt.getTime() <= now.getTime()) {
|
|
19323
|
+
days = 7;
|
|
19324
|
+
executeAt = atLocalDate(now, days, parsed.hour, parsed.minute);
|
|
19325
|
+
}
|
|
19326
|
+
return { executeAt, messageStartIndex: 2 };
|
|
19327
|
+
}
|
|
19328
|
+
return null;
|
|
19329
|
+
}
|
|
19330
|
+
function parseClock(value) {
|
|
19331
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(value);
|
|
19332
|
+
if (!match)
|
|
19333
|
+
return null;
|
|
19334
|
+
const hour = Number(match[1]);
|
|
19335
|
+
const minute = Number(match[2]);
|
|
19336
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
|
19337
|
+
return null;
|
|
19338
|
+
return { hour, minute };
|
|
19339
|
+
}
|
|
19340
|
+
function atLocalDate(now, dayOffset, hour, minute) {
|
|
19341
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + dayOffset, hour, minute, 0, 0);
|
|
19342
|
+
}
|
|
19343
|
+
function validateResult(executeAt, messageStartIndex, tokens, now, pastTodayValue) {
|
|
19344
|
+
if (pastTodayValue)
|
|
19345
|
+
return { ok: false, code: "past_today_time", value: pastTodayValue };
|
|
19346
|
+
if (tokens.slice(messageStartIndex).join(" ").trim().length === 0)
|
|
19347
|
+
return { ok: false, code: "missing_message" };
|
|
19348
|
+
const delta = executeAt.getTime() - now.getTime();
|
|
19349
|
+
if (delta < LATER_MIN_DELAY_MS)
|
|
19350
|
+
return { ok: false, code: "too_soon" };
|
|
19351
|
+
if (delta > LATER_MAX_DELAY_MS)
|
|
19352
|
+
return { ok: false, code: "out_of_range" };
|
|
19353
|
+
return { ok: true, executeAt, messageStartIndex };
|
|
19354
|
+
}
|
|
19355
|
+
var WEEKDAYS;
|
|
19356
|
+
var init_parse_later_time = __esm(() => {
|
|
19357
|
+
init_scheduled_types();
|
|
19358
|
+
WEEKDAYS = new Map([
|
|
19359
|
+
["周日", 0],
|
|
19360
|
+
["周天", 0],
|
|
19361
|
+
["星期日", 0],
|
|
19362
|
+
["星期天", 0],
|
|
19363
|
+
["sun", 0],
|
|
19364
|
+
["sunday", 0],
|
|
19365
|
+
["周一", 1],
|
|
19366
|
+
["星期一", 1],
|
|
19367
|
+
["mon", 1],
|
|
19368
|
+
["monday", 1],
|
|
19369
|
+
["周二", 2],
|
|
19370
|
+
["星期二", 2],
|
|
19371
|
+
["tue", 2],
|
|
19372
|
+
["tuesday", 2],
|
|
19373
|
+
["周三", 3],
|
|
19374
|
+
["星期三", 3],
|
|
19375
|
+
["wed", 3],
|
|
19376
|
+
["wednesday", 3],
|
|
19377
|
+
["周四", 4],
|
|
19378
|
+
["星期四", 4],
|
|
19379
|
+
["thu", 4],
|
|
19380
|
+
["thursday", 4],
|
|
19381
|
+
["周五", 5],
|
|
19382
|
+
["星期五", 5],
|
|
19383
|
+
["fri", 5],
|
|
19384
|
+
["friday", 5],
|
|
19385
|
+
["周六", 6],
|
|
19386
|
+
["星期六", 6],
|
|
19387
|
+
["sat", 6],
|
|
19388
|
+
["saturday", 6]
|
|
19389
|
+
]);
|
|
19390
|
+
});
|
|
19391
|
+
|
|
19392
|
+
// src/commands/handlers/later-handler.ts
|
|
19393
|
+
function handleLaterHelp() {
|
|
19394
|
+
return { text: renderLaterHelp() };
|
|
19395
|
+
}
|
|
19396
|
+
async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
|
|
19397
|
+
let rest = tokens;
|
|
19398
|
+
const seenFlags = new Set;
|
|
19399
|
+
let flagMode;
|
|
19400
|
+
while (rest.length > 0 && (rest[0] === "--bind" || rest[0] === "--temp")) {
|
|
19401
|
+
seenFlags.add(rest[0]);
|
|
19402
|
+
flagMode = rest[0] === "--bind" ? "bound" : "temp";
|
|
19403
|
+
rest = rest.slice(1);
|
|
19404
|
+
}
|
|
19405
|
+
if (seenFlags.size > 1) {
|
|
19406
|
+
return { text: "定时任务的 --bind 与 --temp 不能同时使用。" };
|
|
19407
|
+
}
|
|
19408
|
+
const mode = flagMode ?? defaultMode;
|
|
19409
|
+
if (!currentSession) {
|
|
19410
|
+
return {
|
|
19411
|
+
text: [
|
|
19412
|
+
"当前没有会话,无法创建定时任务。",
|
|
19413
|
+
"",
|
|
19414
|
+
"请先创建或切换到一个会话:",
|
|
19415
|
+
"- /ss codex --ws backend(新建并切换)",
|
|
19416
|
+
"- /use backend-codex(切换到已有会话)"
|
|
19417
|
+
].join(`
|
|
19418
|
+
`)
|
|
19419
|
+
};
|
|
19420
|
+
}
|
|
19421
|
+
const result = parseLaterTime(rest);
|
|
19422
|
+
if (!result.ok) {
|
|
19423
|
+
return { text: renderTimeParseError(result.code, result.value) };
|
|
19424
|
+
}
|
|
19425
|
+
const message = rest.slice(result.messageStartIndex).join(" ").trim();
|
|
19426
|
+
if (message.startsWith("/")) {
|
|
19427
|
+
return {
|
|
19428
|
+
text: [
|
|
19429
|
+
"不支持延迟执行 / 开头的命令。",
|
|
19430
|
+
"",
|
|
19431
|
+
"如果需要让 agent 解释命令,可以用自然语言描述:",
|
|
19432
|
+
"例如:/lt in 1h 请解释 /status 的作用"
|
|
19433
|
+
].join(`
|
|
19434
|
+
`)
|
|
19435
|
+
};
|
|
19436
|
+
}
|
|
19437
|
+
const task = await scheduled.createTask({
|
|
19438
|
+
chatKey,
|
|
19439
|
+
sessionAlias: currentSession.alias,
|
|
19440
|
+
executeAt: result.executeAt,
|
|
19441
|
+
message,
|
|
19442
|
+
sessionMode: mode,
|
|
19443
|
+
...mode === "temp" ? { agent: currentSession.agent, workspace: currentSession.workspace } : {},
|
|
19444
|
+
...accountId ? { accountId } : {},
|
|
19445
|
+
...replyContextToken ? { replyContextToken } : {}
|
|
19446
|
+
});
|
|
19447
|
+
return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
|
|
19448
|
+
}
|
|
19449
|
+
function handleLaterList(scheduled) {
|
|
19450
|
+
const tasks = scheduled.listPending();
|
|
19451
|
+
return { text: renderLaterList(tasks, (alias) => toDisplaySessionAlias(alias)) };
|
|
19452
|
+
}
|
|
19453
|
+
async function handleLaterCancel(id, scheduled) {
|
|
19454
|
+
const ok = await scheduled.cancelPending(id);
|
|
19455
|
+
if (ok) {
|
|
19456
|
+
return { text: `已取消定时任务 #${id.replace(/^#/, "").toLowerCase()}` };
|
|
19457
|
+
}
|
|
19458
|
+
const displayId = id.replace(/^#/, "").toLowerCase();
|
|
19459
|
+
return { text: [`未找到待执行的定时任务 #${displayId}。`, "可以用 /lt list 查看当前待执行任务。"].join(`
|
|
19460
|
+
`) };
|
|
19461
|
+
}
|
|
19462
|
+
function renderTimeParseError(code, value) {
|
|
19463
|
+
switch (code) {
|
|
19464
|
+
case "missing_message":
|
|
19465
|
+
return "定时任务需要消息内容,请在时间后附上要发送的内容。";
|
|
19466
|
+
case "too_soon":
|
|
19467
|
+
return "定时任务执行时间必须在 10 秒之后。";
|
|
19468
|
+
case "out_of_range":
|
|
19469
|
+
return "定时任务执行时间不能超过 7 天。";
|
|
19470
|
+
case "past_today_time":
|
|
19471
|
+
return `今天 ${value} 已经过了,请指定一个未来的时间,或使用「明天」。`;
|
|
19472
|
+
case "unrecognized_time":
|
|
19473
|
+
case "missing_time":
|
|
19474
|
+
default:
|
|
19475
|
+
return [
|
|
19476
|
+
"无法识别时间格式。",
|
|
19477
|
+
"",
|
|
19478
|
+
"支持的格式:",
|
|
19479
|
+
"- /lt in 2h 消息(2小时后)",
|
|
19480
|
+
"- /lt 30分钟后 消息",
|
|
19481
|
+
"- /lt tomorrow 09:00 消息",
|
|
19482
|
+
"- /lt 周五 09:00 消息"
|
|
19483
|
+
].join(`
|
|
19484
|
+
`);
|
|
19485
|
+
}
|
|
19486
|
+
}
|
|
19487
|
+
var laterHelpMetadata;
|
|
19488
|
+
var init_later_handler = __esm(() => {
|
|
19489
|
+
init_parse_later_time();
|
|
19490
|
+
init_scheduled_render();
|
|
19491
|
+
init_channel_scope();
|
|
19492
|
+
laterHelpMetadata = {
|
|
19493
|
+
topic: "later",
|
|
19494
|
+
aliases: ["lt"],
|
|
19495
|
+
summary: "定时任务:到点在临时会话执行(或 --bind 发到当前会话)",
|
|
19496
|
+
commands: [
|
|
19497
|
+
{ usage: "/lt <时间> <消息>", description: "创建定时任务" },
|
|
19498
|
+
{ usage: "/lt --bind <时间> <消息>", description: "改为发送到当前会话" },
|
|
19499
|
+
{ usage: "/lt --temp <时间> <消息>", description: "强制使用临时会话" },
|
|
19500
|
+
{ usage: "/lt list", description: "查看待执行定时任务" },
|
|
19501
|
+
{ usage: "/lt cancel <id>", description: "取消定时任务" }
|
|
19502
|
+
],
|
|
19503
|
+
examples: [
|
|
19504
|
+
"/lt in 2h 检查 CI",
|
|
19505
|
+
"/lt 30分钟后 总结进展",
|
|
19506
|
+
"/lt tomorrow 09:00 看 PR",
|
|
19507
|
+
"/lt 今天 21:30 继续处理",
|
|
19508
|
+
"/lt 周五 09:00 继续处理"
|
|
19509
|
+
],
|
|
19510
|
+
notes: [
|
|
19511
|
+
"只支持一次性任务,不支持重复执行",
|
|
19512
|
+
"时间必须在 10 秒之后、7 天之内",
|
|
19513
|
+
"默认在为本次任务新建的临时会话里执行,跑完即销毁",
|
|
19514
|
+
"加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
|
|
19515
|
+
"/lt list 显示全局待执行任务;群聊中只有群主可取消",
|
|
19516
|
+
"不支持延迟执行 / 开头的 weacpx 命令",
|
|
19517
|
+
"完整时间格式与说明见 docs/later-command.md"
|
|
19518
|
+
]
|
|
19519
|
+
};
|
|
19520
|
+
});
|
|
19521
|
+
|
|
18728
19522
|
// src/commands/help/help-registry.ts
|
|
18729
19523
|
function getHelpTopic(topic) {
|
|
18730
19524
|
return HELP_TOPIC_MAP.get(topic) ?? null;
|
|
@@ -18740,6 +19534,7 @@ var init_help_registry = __esm(() => {
|
|
|
18740
19534
|
init_permission_handler();
|
|
18741
19535
|
init_session_handler();
|
|
18742
19536
|
init_workspace_handler();
|
|
19537
|
+
init_later_handler();
|
|
18743
19538
|
HELP_TOPICS = [
|
|
18744
19539
|
sessionHelp,
|
|
18745
19540
|
workspaceHelp,
|
|
@@ -18750,7 +19545,8 @@ var init_help_registry = __esm(() => {
|
|
|
18750
19545
|
modeHelp,
|
|
18751
19546
|
replyModeHelp,
|
|
18752
19547
|
statusHelp,
|
|
18753
|
-
cancelHelp
|
|
19548
|
+
cancelHelp,
|
|
19549
|
+
laterHelpMetadata
|
|
18754
19550
|
];
|
|
18755
19551
|
HELP_TOPIC_MAP = new Map;
|
|
18756
19552
|
for (const topic of HELP_TOPICS) {
|
|
@@ -18797,7 +19593,8 @@ function renderHelpTopic(topic) {
|
|
|
18797
19593
|
"",
|
|
18798
19594
|
"命令:",
|
|
18799
19595
|
...topic.commands.map((command) => `- ${command.usage} - ${command.description}`),
|
|
18800
|
-
...topic.examples && topic.examples.length > 0 ? ["", "示例:", ...topic.examples.map((example) => `- ${example}`)] : []
|
|
19596
|
+
...topic.examples && topic.examples.length > 0 ? ["", "示例:", ...topic.examples.map((example) => `- ${example}`)] : [],
|
|
19597
|
+
...topic.notes && topic.notes.length > 0 ? ["", "注意:", ...topic.notes.map((note) => `- ${note}`)] : []
|
|
18801
19598
|
].join(`
|
|
18802
19599
|
`);
|
|
18803
19600
|
}
|
|
@@ -18999,6 +19796,15 @@ var init_session_shortcut_handler = __esm(() => {
|
|
|
18999
19796
|
function renderTransportError(session, error2) {
|
|
19000
19797
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19001
19798
|
if (message.includes("No acpx session found")) {
|
|
19799
|
+
if (session.transient) {
|
|
19800
|
+
return {
|
|
19801
|
+
text: [
|
|
19802
|
+
"定时任务的临时会话启动失败,本次任务未能执行。",
|
|
19803
|
+
"临时会话由系统在执行时自动创建,无需手动操作;如需重排,请用 /lt 重新安排。"
|
|
19804
|
+
].join(`
|
|
19805
|
+
`)
|
|
19806
|
+
};
|
|
19807
|
+
}
|
|
19002
19808
|
return {
|
|
19003
19809
|
text: [
|
|
19004
19810
|
`当前会话「${session.alias}」暂时不可用。`,
|
|
@@ -19075,6 +19881,9 @@ async function tryRecoverMissingSession(ops, session, error2) {
|
|
|
19075
19881
|
if (!message.includes("No acpx session found")) {
|
|
19076
19882
|
return null;
|
|
19077
19883
|
}
|
|
19884
|
+
if (session.transient) {
|
|
19885
|
+
return null;
|
|
19886
|
+
}
|
|
19078
19887
|
const transportAgentCommand = await ops.resolveSessionAgentCommand(session);
|
|
19079
19888
|
if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
|
|
19080
19889
|
return null;
|
|
@@ -19448,6 +20257,8 @@ class CommandRouter {
|
|
|
19448
20257
|
resolveSessionAgentCommand;
|
|
19449
20258
|
orchestration;
|
|
19450
20259
|
quota;
|
|
20260
|
+
scheduled;
|
|
20261
|
+
scheduledDelivery;
|
|
19451
20262
|
logger;
|
|
19452
20263
|
autoInstall = autoInstallOptionalDep;
|
|
19453
20264
|
discoverPaths = discoverParentPackagePaths;
|
|
@@ -19457,7 +20268,7 @@ class CommandRouter {
|
|
|
19457
20268
|
__setDiscoverPathsForTest(fn) {
|
|
19458
20269
|
this.discoverPaths = fn;
|
|
19459
20270
|
}
|
|
19460
|
-
constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota) {
|
|
20271
|
+
constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery) {
|
|
19461
20272
|
this.sessions = sessions;
|
|
19462
20273
|
this.transport = transport;
|
|
19463
20274
|
this.config = config2;
|
|
@@ -19465,6 +20276,8 @@ class CommandRouter {
|
|
|
19465
20276
|
this.resolveSessionAgentCommand = resolveSessionAgentCommand;
|
|
19466
20277
|
this.orchestration = orchestration;
|
|
19467
20278
|
this.quota = quota;
|
|
20279
|
+
this.scheduled = scheduled;
|
|
20280
|
+
this.scheduledDelivery = scheduledDelivery;
|
|
19468
20281
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
19469
20282
|
}
|
|
19470
20283
|
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
|
|
@@ -19585,8 +20398,46 @@ class CommandRouter {
|
|
|
19585
20398
|
return await handleTaskReject(this.createHandlerContext(), chatKey, command.taskId);
|
|
19586
20399
|
case "task.cancel":
|
|
19587
20400
|
return await handleTaskCancel(this.createHandlerContext(), chatKey, command.taskId);
|
|
19588
|
-
case "
|
|
19589
|
-
|
|
20401
|
+
case "later.help":
|
|
20402
|
+
if (!this.scheduled)
|
|
20403
|
+
return { text: "定时任务服务未启用。" };
|
|
20404
|
+
return handleLaterHelp();
|
|
20405
|
+
case "later.list":
|
|
20406
|
+
if (!this.scheduled)
|
|
20407
|
+
return { text: "定时任务服务未启用。" };
|
|
20408
|
+
return handleLaterList(this.scheduled);
|
|
20409
|
+
case "later.create": {
|
|
20410
|
+
if (!this.scheduled)
|
|
20411
|
+
return { text: "定时任务服务未启用。" };
|
|
20412
|
+
if (this.scheduledDelivery && !this.scheduledDelivery.supportsScheduledMessages(chatKey)) {
|
|
20413
|
+
return { text: renderLaterUnsupportedChannel() };
|
|
20414
|
+
}
|
|
20415
|
+
const currentSession = await this.sessions.getCurrentSession(chatKey);
|
|
20416
|
+
return await handleLaterCreate(command.tokens, this.scheduled, chatKey, currentSession ? { alias: currentSession.alias, agent: currentSession.agent, workspace: currentSession.workspace } : null, this.config?.later?.defaultMode === "bind" ? "bound" : "temp", accountId, replyContextToken);
|
|
20417
|
+
}
|
|
20418
|
+
case "later.cancel":
|
|
20419
|
+
if (!this.scheduled)
|
|
20420
|
+
return { text: "定时任务服务未启用。" };
|
|
20421
|
+
return await handleLaterCancel(command.id, this.scheduled);
|
|
20422
|
+
case "prompt": {
|
|
20423
|
+
const sessionContext = this.createSessionHandlerContext(undefined, perfSpan);
|
|
20424
|
+
if (metadata?.scheduledSessionDescriptor) {
|
|
20425
|
+
const descriptor = metadata.scheduledSessionDescriptor;
|
|
20426
|
+
const transientSession = {
|
|
20427
|
+
...this.sessions.resolveSession(descriptor.alias, descriptor.agent, descriptor.workspace, descriptor.transportSession),
|
|
20428
|
+
transient: true
|
|
20429
|
+
};
|
|
20430
|
+
return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
20431
|
+
}
|
|
20432
|
+
if (metadata?.scheduledSessionAlias) {
|
|
20433
|
+
const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
|
|
20434
|
+
if (!scheduledSession) {
|
|
20435
|
+
throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
|
|
20436
|
+
}
|
|
20437
|
+
return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
20438
|
+
}
|
|
20439
|
+
return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
|
|
20440
|
+
}
|
|
19590
20441
|
}
|
|
19591
20442
|
});
|
|
19592
20443
|
}
|
|
@@ -19964,11 +20815,13 @@ var init_command_router = __esm(() => {
|
|
|
19964
20815
|
init_agent_handler();
|
|
19965
20816
|
init_workspace_handler();
|
|
19966
20817
|
init_session_shortcut_handler();
|
|
20818
|
+
init_later_handler();
|
|
19967
20819
|
init_session_recovery_handler();
|
|
19968
20820
|
init_auto_install_optional_dep();
|
|
19969
20821
|
init_discover_parent_package_paths();
|
|
19970
20822
|
init_errors();
|
|
19971
20823
|
init_session_reset_handler();
|
|
20824
|
+
init_scheduled_render();
|
|
19972
20825
|
});
|
|
19973
20826
|
|
|
19974
20827
|
// src/config/resolve-acpx-command.ts
|
|
@@ -20061,24 +20914,6 @@ var init_console_agent = __esm(() => {
|
|
|
20061
20914
|
init_command_list();
|
|
20062
20915
|
});
|
|
20063
20916
|
|
|
20064
|
-
// src/orchestration/async-mutex.ts
|
|
20065
|
-
class AsyncMutex {
|
|
20066
|
-
tail = Promise.resolve();
|
|
20067
|
-
async run(critical) {
|
|
20068
|
-
const previous = this.tail;
|
|
20069
|
-
let release;
|
|
20070
|
-
this.tail = new Promise((resolve3) => {
|
|
20071
|
-
release = resolve3;
|
|
20072
|
-
});
|
|
20073
|
-
await previous;
|
|
20074
|
-
try {
|
|
20075
|
-
return await critical();
|
|
20076
|
-
} finally {
|
|
20077
|
-
release();
|
|
20078
|
-
}
|
|
20079
|
-
}
|
|
20080
|
-
}
|
|
20081
|
-
|
|
20082
20917
|
// src/orchestration/orchestration-server.ts
|
|
20083
20918
|
import { rm as rm7 } from "node:fs/promises";
|
|
20084
20919
|
import { createConnection as createConnection2, createServer } from "node:net";
|
|
@@ -20234,6 +21069,12 @@ class OrchestrationServer {
|
|
|
20234
21069
|
reviewId: requireString(params, "reviewId"),
|
|
20235
21070
|
decision: requireEnum(params, "decision", ["accept", "discard"])
|
|
20236
21071
|
});
|
|
21072
|
+
case "scheduled.create":
|
|
21073
|
+
return await this.dispatchScheduledCreate(params);
|
|
21074
|
+
case "scheduled.list":
|
|
21075
|
+
return await this.dispatchScheduledList(params);
|
|
21076
|
+
case "scheduled.cancel":
|
|
21077
|
+
return await this.dispatchScheduledCancel(params);
|
|
20237
21078
|
case "group.new":
|
|
20238
21079
|
requireOnlyKeys(params, ["coordinatorSession", "title"], "params");
|
|
20239
21080
|
return await this.handlers.createGroup({
|
|
@@ -20244,6 +21085,47 @@ class OrchestrationServer {
|
|
|
20244
21085
|
throw new OrchestrationInvalidRequestError(`unsupported orchestration method: ${method}`);
|
|
20245
21086
|
}
|
|
20246
21087
|
}
|
|
21088
|
+
async dispatchScheduledCreate(params) {
|
|
21089
|
+
const input = this.parseScheduledCreateInput(params);
|
|
21090
|
+
const handler = this.deps.createScheduledTaskFromRoute ?? this.handlers.createScheduledTaskFromRoute;
|
|
21091
|
+
if (!handler) {
|
|
21092
|
+
throw new Error("scheduled task creation is not configured");
|
|
21093
|
+
}
|
|
21094
|
+
return await handler(input);
|
|
21095
|
+
}
|
|
21096
|
+
parseScheduledCreateInput(params) {
|
|
21097
|
+
requireOnlyKeys(params, ["coordinatorSession", "timeText", "message", "mode"], "params");
|
|
21098
|
+
const mode = requireOptionalEnum(params, "mode", ["temp", "bound"]);
|
|
21099
|
+
return {
|
|
21100
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
21101
|
+
timeText: requireString(params, "timeText"),
|
|
21102
|
+
message: requireString(params, "message"),
|
|
21103
|
+
...mode !== undefined ? { mode } : {}
|
|
21104
|
+
};
|
|
21105
|
+
}
|
|
21106
|
+
async dispatchScheduledList(params) {
|
|
21107
|
+
requireOnlyKeys(params, ["coordinatorSession"], "params");
|
|
21108
|
+
const input = {
|
|
21109
|
+
coordinatorSession: requireString(params, "coordinatorSession")
|
|
21110
|
+
};
|
|
21111
|
+
const handler = this.deps.listScheduledTasksFromRoute ?? this.handlers.listScheduledTasksFromRoute;
|
|
21112
|
+
if (!handler) {
|
|
21113
|
+
throw new Error("scheduled task listing is not configured");
|
|
21114
|
+
}
|
|
21115
|
+
return await handler(input);
|
|
21116
|
+
}
|
|
21117
|
+
async dispatchScheduledCancel(params) {
|
|
21118
|
+
requireOnlyKeys(params, ["coordinatorSession", "id"], "params");
|
|
21119
|
+
const input = {
|
|
21120
|
+
coordinatorSession: requireString(params, "coordinatorSession"),
|
|
21121
|
+
id: requireString(params, "id")
|
|
21122
|
+
};
|
|
21123
|
+
const handler = this.deps.cancelScheduledTaskFromRoute ?? this.handlers.cancelScheduledTaskFromRoute;
|
|
21124
|
+
if (!handler) {
|
|
21125
|
+
throw new Error("scheduled task cancellation is not configured");
|
|
21126
|
+
}
|
|
21127
|
+
return await handler(input);
|
|
21128
|
+
}
|
|
20247
21129
|
parseRegisterExternalCoordinatorInput(params) {
|
|
20248
21130
|
requireOnlyKeys(params, ["coordinatorSession", "workspace", "defaultTargetAgent"], "params");
|
|
20249
21131
|
const workspace = requireOptionalString(params, "workspace");
|
|
@@ -20588,6 +21470,9 @@ var init_orchestration_server = __esm(() => {
|
|
|
20588
21470
|
"coordinator.retract_answer",
|
|
20589
21471
|
"coordinator.request_human_input",
|
|
20590
21472
|
"coordinator.review_contested_result",
|
|
21473
|
+
"scheduled.create",
|
|
21474
|
+
"scheduled.list",
|
|
21475
|
+
"scheduled.cancel",
|
|
20591
21476
|
"group.new"
|
|
20592
21477
|
]);
|
|
20593
21478
|
});
|
|
@@ -21581,10 +22466,11 @@ class OrchestrationService {
|
|
|
21581
22466
|
const state = await this.deps.loadState();
|
|
21582
22467
|
const now = this.deps.now().toISOString();
|
|
21583
22468
|
const existing = this.ensureCoordinatorRoutes(state)[input.coordinatorSession];
|
|
22469
|
+
const sameChat = existing?.chatKey === input.chatKey;
|
|
21584
22470
|
const hasAccountId = input.accountId !== undefined;
|
|
21585
22471
|
const hasReplyContextToken = input.replyContextToken !== undefined;
|
|
21586
22472
|
const hasCompleteReplyRoute = hasAccountId && hasReplyContextToken;
|
|
21587
|
-
const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken &&
|
|
22473
|
+
const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken && sameChat;
|
|
21588
22474
|
const replyRoute = hasCompleteReplyRoute ? {
|
|
21589
22475
|
accountId: input.accountId,
|
|
21590
22476
|
replyContextToken: input.replyContextToken
|
|
@@ -21595,7 +22481,9 @@ class OrchestrationService {
|
|
|
21595
22481
|
const route = {
|
|
21596
22482
|
coordinatorSession: input.coordinatorSession,
|
|
21597
22483
|
chatKey: input.chatKey,
|
|
22484
|
+
...input.sessionAlias ? { sessionAlias: input.sessionAlias } : {},
|
|
21598
22485
|
...replyRoute ? replyRoute : {},
|
|
22486
|
+
...buildCoordinatorRouteChatMetadata(input, sameChat ? existing : undefined),
|
|
21599
22487
|
updatedAt: now
|
|
21600
22488
|
};
|
|
21601
22489
|
this.ensureCoordinatorRoutes(state)[input.coordinatorSession] = route;
|
|
@@ -23922,6 +24810,22 @@ class OrchestrationService {
|
|
|
23922
24810
|
task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
|
|
23923
24811
|
}
|
|
23924
24812
|
}
|
|
24813
|
+
function buildCoordinatorRouteChatMetadata(input, existing) {
|
|
24814
|
+
const channel = input.channel ?? existing?.channel;
|
|
24815
|
+
const chatType = input.chatType ?? existing?.chatType;
|
|
24816
|
+
const senderId = input.senderId ?? existing?.senderId;
|
|
24817
|
+
const senderName = input.senderName ?? existing?.senderName;
|
|
24818
|
+
const groupId = input.groupId ?? existing?.groupId;
|
|
24819
|
+
const isOwner = input.isOwner ?? existing?.isOwner;
|
|
24820
|
+
return {
|
|
24821
|
+
...channel !== undefined ? { channel } : {},
|
|
24822
|
+
...chatType !== undefined ? { chatType } : {},
|
|
24823
|
+
...senderId !== undefined ? { senderId } : {},
|
|
24824
|
+
...senderName !== undefined ? { senderName } : {},
|
|
24825
|
+
...groupId !== undefined ? { groupId } : {},
|
|
24826
|
+
...isOwner !== undefined ? { isOwner } : {}
|
|
24827
|
+
};
|
|
24828
|
+
}
|
|
23925
24829
|
function isTerminalTaskStatus2(status) {
|
|
23926
24830
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
23927
24831
|
}
|
|
@@ -23965,6 +24869,269 @@ function buildWorkerAnswerPrompt(answer) {
|
|
|
23965
24869
|
`);
|
|
23966
24870
|
}
|
|
23967
24871
|
|
|
24872
|
+
// src/scheduled/scheduled-scheduler.ts
|
|
24873
|
+
class ScheduledTaskScheduler {
|
|
24874
|
+
service;
|
|
24875
|
+
intervalMs;
|
|
24876
|
+
dispatchTimeoutMs;
|
|
24877
|
+
setIntervalFn;
|
|
24878
|
+
clearIntervalFn;
|
|
24879
|
+
dispatchTask;
|
|
24880
|
+
logger;
|
|
24881
|
+
intervalHandle = null;
|
|
24882
|
+
ticking = false;
|
|
24883
|
+
constructor(service, deps) {
|
|
24884
|
+
this.service = service;
|
|
24885
|
+
this.dispatchTask = deps.dispatchTask;
|
|
24886
|
+
this.intervalMs = deps.intervalMs ?? 5000;
|
|
24887
|
+
this.dispatchTimeoutMs = deps.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
|
|
24888
|
+
this.setIntervalFn = deps.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
24889
|
+
this.clearIntervalFn = deps.clearIntervalFn ?? ((timer) => clearInterval(timer));
|
|
24890
|
+
this.logger = deps.logger;
|
|
24891
|
+
}
|
|
24892
|
+
async start() {
|
|
24893
|
+
if (this.intervalHandle !== null)
|
|
24894
|
+
return;
|
|
24895
|
+
await this.service.markStartupMissed();
|
|
24896
|
+
this.intervalHandle = this.setIntervalFn(() => {
|
|
24897
|
+
this.tick();
|
|
24898
|
+
}, this.intervalMs);
|
|
24899
|
+
await this.tick();
|
|
24900
|
+
}
|
|
24901
|
+
stop() {
|
|
24902
|
+
if (this.intervalHandle !== null) {
|
|
24903
|
+
this.clearIntervalFn(this.intervalHandle);
|
|
24904
|
+
this.intervalHandle = null;
|
|
24905
|
+
}
|
|
24906
|
+
}
|
|
24907
|
+
async tick() {
|
|
24908
|
+
if (this.ticking)
|
|
24909
|
+
return;
|
|
24910
|
+
this.ticking = true;
|
|
24911
|
+
try {
|
|
24912
|
+
const dueTasks = await this.service.claimDueTasks();
|
|
24913
|
+
for (const task of dueTasks) {
|
|
24914
|
+
try {
|
|
24915
|
+
await this.dispatchWithTimeout(task);
|
|
24916
|
+
await this.service.markExecuted(task.id);
|
|
24917
|
+
} catch (error2) {
|
|
24918
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
24919
|
+
await this.logger?.error("scheduled.dispatch.failed", "failed to dispatch scheduled task", {
|
|
24920
|
+
taskId: task.id,
|
|
24921
|
+
message
|
|
24922
|
+
});
|
|
24923
|
+
await this.service.markFailed(task.id, error2);
|
|
24924
|
+
}
|
|
24925
|
+
}
|
|
24926
|
+
} finally {
|
|
24927
|
+
this.ticking = false;
|
|
24928
|
+
}
|
|
24929
|
+
}
|
|
24930
|
+
async dispatchWithTimeout(task) {
|
|
24931
|
+
const controller = new AbortController;
|
|
24932
|
+
let timer;
|
|
24933
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
24934
|
+
timer = setTimeout(() => {
|
|
24935
|
+
reject(new Error(`scheduled task dispatch timed out after ${this.dispatchTimeoutMs}ms`));
|
|
24936
|
+
controller.abort();
|
|
24937
|
+
}, this.dispatchTimeoutMs);
|
|
24938
|
+
});
|
|
24939
|
+
const dispatch = this.dispatchTask(task, controller.signal);
|
|
24940
|
+
dispatch.catch(() => {});
|
|
24941
|
+
try {
|
|
24942
|
+
await Promise.race([dispatch, timeout]);
|
|
24943
|
+
} finally {
|
|
24944
|
+
if (timer !== undefined)
|
|
24945
|
+
clearTimeout(timer);
|
|
24946
|
+
}
|
|
24947
|
+
}
|
|
24948
|
+
}
|
|
24949
|
+
var DEFAULT_DISPATCH_TIMEOUT_MS;
|
|
24950
|
+
var init_scheduled_scheduler = __esm(() => {
|
|
24951
|
+
DEFAULT_DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
|
|
24952
|
+
});
|
|
24953
|
+
|
|
24954
|
+
// src/scheduled/scheduled-dispatch.ts
|
|
24955
|
+
function buildScheduledDispatchTask(deps) {
|
|
24956
|
+
return async (task, abortSignal) => {
|
|
24957
|
+
if (task.session_mode === "temp") {
|
|
24958
|
+
await dispatchTemp(task, abortSignal, deps);
|
|
24959
|
+
return;
|
|
24960
|
+
}
|
|
24961
|
+
await dispatchBound(task, abortSignal, deps);
|
|
24962
|
+
};
|
|
24963
|
+
}
|
|
24964
|
+
async function dispatchBound(task, abortSignal, deps) {
|
|
24965
|
+
const session = await deps.getSession(task.session_alias);
|
|
24966
|
+
if (!session) {
|
|
24967
|
+
throw new Error(`session "${task.session_alias}" not found for scheduled task`);
|
|
24968
|
+
}
|
|
24969
|
+
const noticeText = `执行定时任务 #${task.id}
|
|
24970
|
+
会话:${toDisplaySessionAlias(task.session_alias)}
|
|
24971
|
+
内容:${preview(task.message)}`;
|
|
24972
|
+
await deps.sendScheduledMessage({
|
|
24973
|
+
chatKey: task.chat_key,
|
|
24974
|
+
taskId: task.id,
|
|
24975
|
+
sessionAlias: task.session_alias,
|
|
24976
|
+
noticeText,
|
|
24977
|
+
promptText: task.message,
|
|
24978
|
+
abortSignal,
|
|
24979
|
+
...task.account_id ? { accountId: task.account_id } : {},
|
|
24980
|
+
...task.reply_context_token ? { replyContextToken: task.reply_context_token } : {}
|
|
24981
|
+
});
|
|
24982
|
+
}
|
|
24983
|
+
async function dispatchTemp(task, abortSignal, deps) {
|
|
24984
|
+
if (!task.agent || !task.workspace) {
|
|
24985
|
+
throw new Error(`temp scheduled task #${task.id} is missing its agent/workspace snapshot`);
|
|
24986
|
+
}
|
|
24987
|
+
const alias = `later-${task.id}`;
|
|
24988
|
+
const transportSession = `${task.workspace}:${alias}`;
|
|
24989
|
+
const session = deps.resolveSession(alias, task.agent, task.workspace, transportSession);
|
|
24990
|
+
const noticeText = `执行定时任务 #${task.id}
|
|
24991
|
+
会话:临时会话(${task.workspace} · ${task.agent})
|
|
24992
|
+
内容:${preview(task.message)}`;
|
|
24993
|
+
try {
|
|
24994
|
+
await deps.sendScheduledMessage({
|
|
24995
|
+
chatKey: task.chat_key,
|
|
24996
|
+
taskId: task.id,
|
|
24997
|
+
sessionAlias: task.session_alias,
|
|
24998
|
+
sessionDescriptor: { alias, agent: task.agent, workspace: task.workspace, transportSession },
|
|
24999
|
+
noticeText,
|
|
25000
|
+
promptText: task.message,
|
|
25001
|
+
abortSignal,
|
|
25002
|
+
...task.account_id ? { accountId: task.account_id } : {},
|
|
25003
|
+
...task.reply_context_token ? { replyContextToken: task.reply_context_token } : {}
|
|
25004
|
+
});
|
|
25005
|
+
} finally {
|
|
25006
|
+
if (deps.removeSession) {
|
|
25007
|
+
try {
|
|
25008
|
+
await deps.removeSession(session);
|
|
25009
|
+
} catch (error2) {
|
|
25010
|
+
await deps.logger?.error("scheduled.temp_session_close_failed", "failed to close temp scheduled session", { taskId: task.id, transportSession, error: String(error2) });
|
|
25011
|
+
}
|
|
25012
|
+
}
|
|
25013
|
+
}
|
|
25014
|
+
}
|
|
25015
|
+
var init_scheduled_dispatch = __esm(() => {
|
|
25016
|
+
init_channel_scope();
|
|
25017
|
+
init_scheduled_render();
|
|
25018
|
+
});
|
|
25019
|
+
|
|
25020
|
+
// src/scheduled/scheduled-route-create.ts
|
|
25021
|
+
async function createScheduledTaskFromRoute(input, deps) {
|
|
25022
|
+
const coordinatorSession = input.coordinatorSession.trim();
|
|
25023
|
+
if (coordinatorSession.length === 0) {
|
|
25024
|
+
throw new Error("coordinatorSession must be a non-empty string");
|
|
25025
|
+
}
|
|
25026
|
+
const route = deps.state.orchestration.coordinatorRoutes[coordinatorSession];
|
|
25027
|
+
if (!route) {
|
|
25028
|
+
throw new Error(`no chat route is recorded for coordinator session "${coordinatorSession}"`);
|
|
25029
|
+
}
|
|
25030
|
+
if (route.chatType !== "direct" && route.chatType !== "group") {
|
|
25031
|
+
throw new Error("scheduled_create requires current chat route metadata");
|
|
25032
|
+
}
|
|
25033
|
+
if (route.chatType === "group" && route.isOwner !== true) {
|
|
25034
|
+
throw new Error("scheduled_create is owner-only in group chats");
|
|
25035
|
+
}
|
|
25036
|
+
if (deps.supportsScheduledMessages && !deps.supportsScheduledMessages(route.chatKey)) {
|
|
25037
|
+
throw new Error("current channel does not support scheduled tasks");
|
|
25038
|
+
}
|
|
25039
|
+
const message = input.message.trim();
|
|
25040
|
+
if (message.length === 0) {
|
|
25041
|
+
throw new Error("message must be a non-empty string");
|
|
25042
|
+
}
|
|
25043
|
+
if (message.startsWith("/")) {
|
|
25044
|
+
throw new Error("scheduled_create does not support slash-prefixed weacpx commands");
|
|
25045
|
+
}
|
|
25046
|
+
if (!route.sessionAlias) {
|
|
25047
|
+
throw new Error("scheduled_create requires current session route metadata");
|
|
25048
|
+
}
|
|
25049
|
+
const session = await deps.sessions.getSession(route.sessionAlias);
|
|
25050
|
+
if (!session) {
|
|
25051
|
+
throw new Error(`session "${route.sessionAlias}" recorded for coordinator session "${coordinatorSession}" was not found`);
|
|
25052
|
+
}
|
|
25053
|
+
if (session.transportSession !== coordinatorSession) {
|
|
25054
|
+
throw new Error(`session "${route.sessionAlias}" is no longer attached to coordinator session "${coordinatorSession}"`);
|
|
25055
|
+
}
|
|
25056
|
+
const executeAt = parseRouteScheduledTime(input.timeText, deps.now?.() ?? new Date);
|
|
25057
|
+
const mode = input.mode ?? (deps.config.later?.defaultMode === "bind" ? "bound" : "temp");
|
|
25058
|
+
return await deps.scheduled.createTask({
|
|
25059
|
+
chatKey: route.chatKey,
|
|
25060
|
+
sessionAlias: session.alias,
|
|
25061
|
+
executeAt,
|
|
25062
|
+
message,
|
|
25063
|
+
sessionMode: mode,
|
|
25064
|
+
...mode === "temp" ? { agent: session.agent, workspace: session.workspace } : {},
|
|
25065
|
+
...route.accountId ? { accountId: route.accountId } : {},
|
|
25066
|
+
...route.replyContextToken ? { replyContextToken: route.replyContextToken } : {},
|
|
25067
|
+
sourceLabel: "mcp:scheduled_create"
|
|
25068
|
+
});
|
|
25069
|
+
}
|
|
25070
|
+
function parseRouteScheduledTime(timeText, now) {
|
|
25071
|
+
const timeTokens = timeText.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
25072
|
+
if (timeTokens.length === 0) {
|
|
25073
|
+
throw new Error(formatLaterTimeParseError("missing_time"));
|
|
25074
|
+
}
|
|
25075
|
+
const parsed = parseLaterTime([...timeTokens, "__scheduled_create_message__"], now);
|
|
25076
|
+
if (!parsed.ok) {
|
|
25077
|
+
throw new Error(formatLaterTimeParseError(parsed.code, parsed.value));
|
|
25078
|
+
}
|
|
25079
|
+
if (parsed.messageStartIndex !== timeTokens.length) {
|
|
25080
|
+
throw new Error("timeText must contain only the time expression; put the scheduled content in message");
|
|
25081
|
+
}
|
|
25082
|
+
return parsed.executeAt;
|
|
25083
|
+
}
|
|
25084
|
+
function formatLaterTimeParseError(code, value) {
|
|
25085
|
+
switch (code) {
|
|
25086
|
+
case "missing_message":
|
|
25087
|
+
return "message must be provided separately from timeText";
|
|
25088
|
+
case "too_soon":
|
|
25089
|
+
return "scheduled task time must be at least 10 seconds in the future";
|
|
25090
|
+
case "out_of_range":
|
|
25091
|
+
return "scheduled task time must be within 7 days";
|
|
25092
|
+
case "past_today_time":
|
|
25093
|
+
return `today at ${value} has already passed; choose a future time or use tomorrow`;
|
|
25094
|
+
case "unrecognized_time":
|
|
25095
|
+
case "missing_time":
|
|
25096
|
+
default:
|
|
25097
|
+
return "unrecognized timeText; supported examples: in 2h, 30分钟后, tomorrow 09:00, 周五 09:00";
|
|
25098
|
+
}
|
|
25099
|
+
}
|
|
25100
|
+
var init_scheduled_route_create = __esm(() => {
|
|
25101
|
+
init_parse_later_time();
|
|
25102
|
+
});
|
|
25103
|
+
|
|
25104
|
+
// src/scheduled/scheduled-route-manage.ts
|
|
25105
|
+
async function listScheduledTasksFromRoute(input, deps) {
|
|
25106
|
+
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
|
|
25107
|
+
return deps.scheduled.listPending();
|
|
25108
|
+
}
|
|
25109
|
+
async function cancelScheduledTaskFromRoute(input, deps) {
|
|
25110
|
+
resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
|
|
25111
|
+
const cancelled = await deps.scheduled.cancelPending(input.id);
|
|
25112
|
+
return { id: normalizeId(input.id), cancelled };
|
|
25113
|
+
}
|
|
25114
|
+
function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
|
|
25115
|
+
const session = coordinatorSession.trim();
|
|
25116
|
+
if (session.length === 0) {
|
|
25117
|
+
throw new Error("coordinatorSession must be a non-empty string");
|
|
25118
|
+
}
|
|
25119
|
+
const route = state.orchestration.coordinatorRoutes[session];
|
|
25120
|
+
if (!route) {
|
|
25121
|
+
throw new Error(`no chat route is recorded for coordinator session "${session}"`);
|
|
25122
|
+
}
|
|
25123
|
+
if (route.chatType !== "direct" && route.chatType !== "group") {
|
|
25124
|
+
throw new Error(`${label} requires current chat route metadata`);
|
|
25125
|
+
}
|
|
25126
|
+
if (route.chatType === "group" && route.isOwner !== true) {
|
|
25127
|
+
throw new Error(`${label} is owner-only in group chats`);
|
|
25128
|
+
}
|
|
25129
|
+
return route;
|
|
25130
|
+
}
|
|
25131
|
+
var init_scheduled_route_manage = __esm(() => {
|
|
25132
|
+
init_scheduled_service();
|
|
25133
|
+
});
|
|
25134
|
+
|
|
23968
25135
|
// src/sessions/session-service.ts
|
|
23969
25136
|
class SessionService {
|
|
23970
25137
|
config;
|
|
@@ -24419,20 +25586,47 @@ async function runConsole(paths, deps) {
|
|
|
24419
25586
|
runtimeForGc.orchestration.service.purgeExpiredResetCoordinators({ cutoffDays: 7, trigger: "interval" }).catch(() => {});
|
|
24420
25587
|
}, 86400000);
|
|
24421
25588
|
}
|
|
25589
|
+
const channelStartPromise = deps.channels.startAll({
|
|
25590
|
+
agent: runtime.agent,
|
|
25591
|
+
abortSignal: shutdownController.signal,
|
|
25592
|
+
quota: runtime.quota,
|
|
25593
|
+
logger: runtime.logger,
|
|
25594
|
+
perfTracer: runtime.perfTracer
|
|
25595
|
+
});
|
|
25596
|
+
channelStartPromise.catch(() => {});
|
|
25597
|
+
let channelStartSettled = false;
|
|
25598
|
+
let channelStartError;
|
|
25599
|
+
channelStartPromise.then(() => {
|
|
25600
|
+
channelStartSettled = true;
|
|
25601
|
+
}, (error2) => {
|
|
25602
|
+
channelStartSettled = true;
|
|
25603
|
+
channelStartError = error2;
|
|
25604
|
+
});
|
|
25605
|
+
await Promise.resolve();
|
|
25606
|
+
if (channelStartSettled && channelStartError) {
|
|
25607
|
+
if (deps.channelStartupPolicy !== "best-effort") {
|
|
25608
|
+
throw channelStartError;
|
|
25609
|
+
}
|
|
25610
|
+
await runtime.logger.error("daemon.channels.start_failed", "all channels failed to start; daemon remains alive for orchestration IPC", { error: channelStartError instanceof Error ? channelStartError.message : String(channelStartError) });
|
|
25611
|
+
await waitForShutdown(shutdownController.signal);
|
|
25612
|
+
return;
|
|
25613
|
+
}
|
|
24422
25614
|
try {
|
|
24423
|
-
await
|
|
24424
|
-
|
|
24425
|
-
|
|
24426
|
-
|
|
24427
|
-
|
|
24428
|
-
|
|
24429
|
-
|
|
25615
|
+
await runtime.scheduled.scheduler.start();
|
|
25616
|
+
} catch (error2) {
|
|
25617
|
+
shutdownController.abort();
|
|
25618
|
+
throw error2;
|
|
25619
|
+
}
|
|
25620
|
+
try {
|
|
25621
|
+
await channelStartPromise;
|
|
24430
25622
|
} catch (error2) {
|
|
25623
|
+
runtime.scheduled.scheduler.stop();
|
|
24431
25624
|
if (deps.channelStartupPolicy !== "best-effort") {
|
|
24432
25625
|
throw error2;
|
|
24433
25626
|
}
|
|
24434
25627
|
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) });
|
|
24435
25628
|
await waitForShutdown(shutdownController.signal);
|
|
25629
|
+
return;
|
|
24436
25630
|
}
|
|
24437
25631
|
} finally {
|
|
24438
25632
|
await runCleanupSequence({
|
|
@@ -25573,7 +26767,7 @@ function buildWeacpxMcpServerSpec(input) {
|
|
|
25573
26767
|
"mcp-stdio",
|
|
25574
26768
|
"--coordinator-session",
|
|
25575
26769
|
input.coordinatorSession,
|
|
25576
|
-
...input.sourceHandle ? ["--source-handle", input.sourceHandle] : []
|
|
26770
|
+
...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
|
|
25577
26771
|
]
|
|
25578
26772
|
};
|
|
25579
26773
|
}
|
|
@@ -26435,6 +27629,21 @@ class MessageChannelRegistry {
|
|
|
26435
27629
|
async sendCoordinatorMessage(input) {
|
|
26436
27630
|
await this.requireByChatKey(input.chatKey).sendCoordinatorMessage(input);
|
|
26437
27631
|
}
|
|
27632
|
+
supportsScheduledMessages(chatKey) {
|
|
27633
|
+
const [candidateChannelId] = chatKey.split(":", 1);
|
|
27634
|
+
if (chatKey.includes(":") && candidateChannelId && !this.channels.has(candidateChannelId)) {
|
|
27635
|
+
return false;
|
|
27636
|
+
}
|
|
27637
|
+
const channel = this.getByChatKey(chatKey);
|
|
27638
|
+
return !!channel?.sendScheduledMessage;
|
|
27639
|
+
}
|
|
27640
|
+
async sendScheduledMessage(input) {
|
|
27641
|
+
const channel = this.requireByChatKey(input.chatKey);
|
|
27642
|
+
if (!channel.sendScheduledMessage) {
|
|
27643
|
+
throw new Error(`channel '${channel.id}' does not support scheduled messages`);
|
|
27644
|
+
}
|
|
27645
|
+
await channel.sendScheduledMessage(input);
|
|
27646
|
+
}
|
|
26438
27647
|
createConsumerLocks() {
|
|
26439
27648
|
const result = [];
|
|
26440
27649
|
for (const channel of this.channels.values()) {
|
|
@@ -26649,6 +27858,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
26649
27858
|
}
|
|
26650
27859
|
});
|
|
26651
27860
|
const sessions = new SessionService(config2, debouncedStateStore, state, { stateMutex });
|
|
27861
|
+
const scheduledService = new ScheduledTaskService(state, debouncedStateStore, { stateMutex });
|
|
26652
27862
|
const pendingWorkerDispatches = new Set;
|
|
26653
27863
|
const transport = config2.transport.type === "acpx-bridge" ? await (deps.createBridgeTransport?.() ?? Promise.resolve(new AcpxBridgeTransport(await spawnAcpxBridgeClient({
|
|
26654
27864
|
acpxCommand,
|
|
@@ -27007,9 +28217,34 @@ async function buildApp(paths, deps = {}) {
|
|
|
27007
28217
|
}
|
|
27008
28218
|
const progressHeartbeatInterval = startProgressHeartbeat(orchestration, config2, logger2, deps.channel ?? null);
|
|
27009
28219
|
const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
|
|
27010
|
-
const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration
|
|
27011
|
-
|
|
28220
|
+
const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration, {
|
|
28221
|
+
createScheduledTaskFromRoute: async (input) => await createScheduledTaskFromRoute(input, {
|
|
28222
|
+
state,
|
|
28223
|
+
config: config2,
|
|
28224
|
+
sessions,
|
|
28225
|
+
scheduled: scheduledService,
|
|
28226
|
+
...deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : {}
|
|
28227
|
+
}),
|
|
28228
|
+
listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
|
|
28229
|
+
cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
|
|
28230
|
+
});
|
|
28231
|
+
const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined);
|
|
27012
28232
|
const agent = new ConsoleAgent(router, logger2);
|
|
28233
|
+
const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
|
|
28234
|
+
dispatchTask: buildScheduledDispatchTask({
|
|
28235
|
+
getSession: (alias) => sessions.getSession(alias),
|
|
28236
|
+
resolveSession: (alias, agent2, workspace, transportSession) => sessions.resolveSession(alias, agent2, workspace, transportSession),
|
|
28237
|
+
sendScheduledMessage: async (input) => {
|
|
28238
|
+
if (!deps.channel?.sendScheduledMessage) {
|
|
28239
|
+
throw new Error("no channel runtime available for scheduled task dispatch");
|
|
28240
|
+
}
|
|
28241
|
+
await deps.channel.sendScheduledMessage(input);
|
|
28242
|
+
},
|
|
28243
|
+
...transport.removeSession ? { removeSession: (session) => transport.removeSession(session) } : {},
|
|
28244
|
+
logger: logger2
|
|
28245
|
+
}),
|
|
28246
|
+
logger: logger2
|
|
28247
|
+
});
|
|
27013
28248
|
return {
|
|
27014
28249
|
agent,
|
|
27015
28250
|
router,
|
|
@@ -27025,7 +28260,12 @@ async function buildApp(paths, deps = {}) {
|
|
|
27025
28260
|
server: orchestrationServer,
|
|
27026
28261
|
endpoint: orchestrationEndpoint
|
|
27027
28262
|
},
|
|
28263
|
+
scheduled: {
|
|
28264
|
+
service: scheduledService,
|
|
28265
|
+
scheduler: scheduledScheduler
|
|
28266
|
+
},
|
|
27028
28267
|
dispose: async () => {
|
|
28268
|
+
scheduledScheduler.stop();
|
|
27029
28269
|
if (progressHeartbeatInterval !== undefined) {
|
|
27030
28270
|
clearInterval(progressHeartbeatInterval);
|
|
27031
28271
|
}
|
|
@@ -27078,6 +28318,7 @@ function replaceRuntimeState(target, source) {
|
|
|
27078
28318
|
target.sessions = source.sessions;
|
|
27079
28319
|
target.chat_contexts = source.chat_contexts;
|
|
27080
28320
|
target.orchestration = source.orchestration;
|
|
28321
|
+
target.scheduled_tasks = source.scheduled_tasks;
|
|
27081
28322
|
}
|
|
27082
28323
|
function replaceRuntimeConfig(target, source) {
|
|
27083
28324
|
Object.assign(target, source);
|
|
@@ -27172,6 +28413,11 @@ var init_main = __esm(async () => {
|
|
|
27172
28413
|
init_orchestration_server();
|
|
27173
28414
|
init_orchestration_service();
|
|
27174
28415
|
init_build_coordinator_prompt();
|
|
28416
|
+
init_scheduled_scheduler();
|
|
28417
|
+
init_scheduled_service();
|
|
28418
|
+
init_scheduled_dispatch();
|
|
28419
|
+
init_scheduled_route_create();
|
|
28420
|
+
init_scheduled_route_manage();
|
|
27175
28421
|
init_session_service();
|
|
27176
28422
|
init_state_store();
|
|
27177
28423
|
init_run_console();
|
|
@@ -40703,12 +41949,13 @@ var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
|
|
|
40703
41949
|
var orderSchema = exports_external.enum(["asc", "desc"]);
|
|
40704
41950
|
var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
|
|
40705
41951
|
var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
|
|
41952
|
+
var scheduledModeSchema = exports_external.enum(["temp", "bound"]);
|
|
40706
41953
|
var taskQuestionSchema = exports_external.object({
|
|
40707
41954
|
taskId: exports_external.string().min(1),
|
|
40708
41955
|
questionId: exports_external.string().min(1)
|
|
40709
41956
|
}).strict();
|
|
40710
41957
|
function buildWeacpxMcpToolRegistry(input) {
|
|
40711
|
-
const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, availableAgents } = input;
|
|
41958
|
+
const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, internalSessionTools, availableAgents } = input;
|
|
40712
41959
|
const tools = [
|
|
40713
41960
|
{
|
|
40714
41961
|
name: "delegate_request",
|
|
@@ -40926,6 +42173,63 @@ function buildWeacpxMcpToolRegistry(input) {
|
|
|
40926
42173
|
})
|
|
40927
42174
|
}
|
|
40928
42175
|
];
|
|
42176
|
+
if (internalSessionTools && !isExternalCoordinator && !sourceHandle) {
|
|
42177
|
+
tools.push({
|
|
42178
|
+
name: "scheduled_create",
|
|
42179
|
+
description: "Create a one-shot scheduled task for the current conversation session using the recorded chat route. Provide only the time expression and message; routing/session/account details are resolved by weacpx.",
|
|
42180
|
+
inputSchema: exports_external.object({
|
|
42181
|
+
timeText: exports_external.string().min(1).describe("Time expression, e.g. 'in 2h', '30分钟后', 'tomorrow 09:00', or '周五 09:00'."),
|
|
42182
|
+
message: exports_external.string().min(1).describe("Natural-language message to send to the current session at the scheduled time."),
|
|
42183
|
+
mode: scheduledModeSchema.describe("'temp' creates a temporary one-shot session; 'bound' sends to the current bound session.").optional()
|
|
42184
|
+
}).strict(),
|
|
42185
|
+
handler: async (args) => await asToolResult(async () => {
|
|
42186
|
+
const input2 = args;
|
|
42187
|
+
const task = await transport.scheduledCreate({
|
|
42188
|
+
coordinatorSession,
|
|
42189
|
+
timeText: input2.timeText,
|
|
42190
|
+
message: input2.message,
|
|
42191
|
+
...input2.mode ? { mode: input2.mode } : {}
|
|
42192
|
+
});
|
|
42193
|
+
return createSuccessResult(`Scheduled task #${task.id} created for ${task.execute_at}.`, {
|
|
42194
|
+
id: task.id,
|
|
42195
|
+
status: task.status,
|
|
42196
|
+
executeAt: task.execute_at,
|
|
42197
|
+
sessionAlias: task.session_alias,
|
|
42198
|
+
sessionMode: task.session_mode ?? "bound"
|
|
42199
|
+
});
|
|
42200
|
+
})
|
|
42201
|
+
});
|
|
42202
|
+
tools.push({
|
|
42203
|
+
name: "scheduled_list",
|
|
42204
|
+
description: "List pending one-shot scheduled tasks (global). Use to recover task ids before cancelling, or to see what is scheduled. Owner-only in group chats. Routing and account are resolved from the current session; pass no other arguments.",
|
|
42205
|
+
inputSchema: exports_external.object({}).strict(),
|
|
42206
|
+
handler: async () => await asToolResult(async () => {
|
|
42207
|
+
const tasks = await transport.scheduledList({ coordinatorSession });
|
|
42208
|
+
return createSuccessResult(renderScheduledList(tasks), {
|
|
42209
|
+
tasks: tasks.map((task) => ({
|
|
42210
|
+
id: task.id,
|
|
42211
|
+
executeAt: task.execute_at,
|
|
42212
|
+
message: task.message,
|
|
42213
|
+
sessionAlias: task.session_alias,
|
|
42214
|
+
sessionMode: task.session_mode ?? "bound",
|
|
42215
|
+
chatKey: task.chat_key
|
|
42216
|
+
}))
|
|
42217
|
+
});
|
|
42218
|
+
})
|
|
42219
|
+
});
|
|
42220
|
+
tools.push({
|
|
42221
|
+
name: "scheduled_cancel",
|
|
42222
|
+
description: "Cancel a pending scheduled task by id. Owner-only in group chats. Returns whether a pending task with that id was found and cancelled. Routing is resolved from the current session.",
|
|
42223
|
+
inputSchema: exports_external.object({
|
|
42224
|
+
id: exports_external.string().min(1).describe("The scheduled task id, e.g. 'k8f2' (a leading # is allowed).")
|
|
42225
|
+
}).strict(),
|
|
42226
|
+
handler: async (args) => await asToolResult(async () => {
|
|
42227
|
+
const { id } = args;
|
|
42228
|
+
const result = await transport.scheduledCancel({ coordinatorSession, id });
|
|
42229
|
+
return createSuccessResult(renderScheduledCancel(result), { id: result.id, cancelled: result.cancelled });
|
|
42230
|
+
})
|
|
42231
|
+
});
|
|
42232
|
+
}
|
|
40929
42233
|
if (isExternalCoordinator) {
|
|
40930
42234
|
const externalCoordinatorIncompatibleTools = new Set([
|
|
40931
42235
|
"coordinator_request_human_input"
|
|
@@ -41005,6 +42309,19 @@ function renderTaskList(tasks) {
|
|
|
41005
42309
|
return ["Tasks for the current coordinator:", ...tasks.map((task) => renderTaskListItem(task))].join(`
|
|
41006
42310
|
`);
|
|
41007
42311
|
}
|
|
42312
|
+
function renderScheduledList(tasks) {
|
|
42313
|
+
if (tasks.length === 0) {
|
|
42314
|
+
return "There are no pending scheduled tasks.";
|
|
42315
|
+
}
|
|
42316
|
+
return [
|
|
42317
|
+
"Pending scheduled tasks:",
|
|
42318
|
+
...tasks.map((task) => `- #${task.id} at ${task.execute_at} [${task.session_mode ?? "bound"}] -> ${task.session_alias}: ${task.message}`)
|
|
42319
|
+
].join(`
|
|
42320
|
+
`);
|
|
42321
|
+
}
|
|
42322
|
+
function renderScheduledCancel(result) {
|
|
42323
|
+
return result.cancelled ? `Scheduled task #${result.id} cancelled.` : `No pending scheduled task #${result.id} found.`;
|
|
42324
|
+
}
|
|
41008
42325
|
function renderTaskListItem(task) {
|
|
41009
42326
|
const role = task.role ? ` / ${task.role}` : "";
|
|
41010
42327
|
const group = task.groupId ? `; group: ${task.groupId}` : "";
|
|
@@ -41175,6 +42492,15 @@ class OrchestrationClient {
|
|
|
41175
42492
|
async createGroup(input) {
|
|
41176
42493
|
return await this.request("group.new", input);
|
|
41177
42494
|
}
|
|
42495
|
+
async scheduledCreate(input) {
|
|
42496
|
+
return await this.request("scheduled.create", input);
|
|
42497
|
+
}
|
|
42498
|
+
async scheduledList(input) {
|
|
42499
|
+
return await this.request("scheduled.list", input);
|
|
42500
|
+
}
|
|
42501
|
+
async scheduledCancel(input) {
|
|
42502
|
+
return await this.request("scheduled.cancel", input);
|
|
42503
|
+
}
|
|
41178
42504
|
async request(method, params, timeoutMs = this.timeoutMs) {
|
|
41179
42505
|
const id = this.createId();
|
|
41180
42506
|
return await new Promise((resolve, reject) => {
|
|
@@ -41287,7 +42613,25 @@ function createOrchestrationTransport(endpoint, deps = {}) {
|
|
|
41287
42613
|
},
|
|
41288
42614
|
coordinatorAnswerQuestion: async (input) => await client.coordinatorAnswerQuestion(input),
|
|
41289
42615
|
coordinatorRequestHumanInput: async (input) => await client.coordinatorRequestHumanInput(input),
|
|
41290
|
-
coordinatorReviewContestedResult: async (input) => await client.coordinatorReviewContestedResult(input)
|
|
42616
|
+
coordinatorReviewContestedResult: async (input) => await client.coordinatorReviewContestedResult(input),
|
|
42617
|
+
scheduledCreate: async (input) => {
|
|
42618
|
+
if (!client.scheduledCreate) {
|
|
42619
|
+
throw new Error("orchestration client scheduledCreate is not configured");
|
|
42620
|
+
}
|
|
42621
|
+
return await client.scheduledCreate(input);
|
|
42622
|
+
},
|
|
42623
|
+
scheduledList: async (input) => {
|
|
42624
|
+
if (!client.scheduledList) {
|
|
42625
|
+
throw new Error("orchestration client scheduledList is not configured");
|
|
42626
|
+
}
|
|
42627
|
+
return await client.scheduledList(input);
|
|
42628
|
+
},
|
|
42629
|
+
scheduledCancel: async (input) => {
|
|
42630
|
+
if (!client.scheduledCancel) {
|
|
42631
|
+
throw new Error("orchestration client scheduledCancel is not configured");
|
|
42632
|
+
}
|
|
42633
|
+
return await client.scheduledCancel(input);
|
|
42634
|
+
}
|
|
41291
42635
|
};
|
|
41292
42636
|
}
|
|
41293
42637
|
|
|
@@ -41343,6 +42687,7 @@ function createWeacpxMcpServer(options) {
|
|
|
41343
42687
|
coordinatorSession: identity.coordinatorSession,
|
|
41344
42688
|
...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {},
|
|
41345
42689
|
...identity.isExternalCoordinator ? { isExternalCoordinator: true } : {},
|
|
42690
|
+
...identity.internalSessionTools ? { internalSessionTools: true } : {},
|
|
41346
42691
|
...options.availableAgents ? { availableAgents: options.availableAgents } : {}
|
|
41347
42692
|
});
|
|
41348
42693
|
return toolState;
|
|
@@ -41778,7 +43123,8 @@ async function resolveMcpIdentity(server, options) {
|
|
|
41778
43123
|
return {
|
|
41779
43124
|
coordinatorSession: options.coordinatorSession,
|
|
41780
43125
|
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
|
|
41781
|
-
...options.isExternalCoordinator ? { isExternalCoordinator: true } : {}
|
|
43126
|
+
...options.isExternalCoordinator ? { isExternalCoordinator: true } : {},
|
|
43127
|
+
...options.internalSessionTools ? { internalSessionTools: true } : {}
|
|
41782
43128
|
};
|
|
41783
43129
|
}
|
|
41784
43130
|
throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
|
|
@@ -41867,6 +43213,7 @@ async function runWeacpxMcpServer(options) {
|
|
|
41867
43213
|
transport,
|
|
41868
43214
|
...options.coordinatorSession ? { coordinatorSession: options.coordinatorSession } : {},
|
|
41869
43215
|
...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
|
|
43216
|
+
...options.internalSessionTools ? { internalSessionTools: true } : {},
|
|
41870
43217
|
...options.resolveIdentity ? { resolveIdentity: options.resolveIdentity } : {},
|
|
41871
43218
|
...options.availableAgents ? { availableAgents: options.availableAgents } : {}
|
|
41872
43219
|
});
|
|
@@ -41961,6 +43308,11 @@ function parseCoordinatorSession(args, env = process.env) {
|
|
|
41961
43308
|
});
|
|
41962
43309
|
}
|
|
41963
43310
|
|
|
43311
|
+
// src/mcp/parse-internal-session-tools.ts
|
|
43312
|
+
function parseInternalSessionToolsFlag(args, _env = process.env) {
|
|
43313
|
+
return args.includes("--internal-session-tools");
|
|
43314
|
+
}
|
|
43315
|
+
|
|
41964
43316
|
// src/mcp/parse-source-handle.ts
|
|
41965
43317
|
function parseSourceHandle(args, env = process.env) {
|
|
41966
43318
|
return parseStringFlag(args, env, {
|
|
@@ -41973,13 +43325,19 @@ function parseSourceHandle(args, env = process.env) {
|
|
|
41973
43325
|
init_workspace_path();
|
|
41974
43326
|
init_workspace_name();
|
|
41975
43327
|
init_state_store();
|
|
43328
|
+
init_channel_scope();
|
|
43329
|
+
init_scheduled_render();
|
|
43330
|
+
init_scheduled_service();
|
|
41976
43331
|
|
|
41977
43332
|
// src/onboarding.ts
|
|
41978
43333
|
init_workspace_path();
|
|
41979
43334
|
init_workspace_name();
|
|
43335
|
+
init_default_workspace();
|
|
41980
43336
|
init_agent_templates();
|
|
41981
43337
|
function isFirstUse(config2, state) {
|
|
41982
|
-
|
|
43338
|
+
const workspaceNames = Object.keys(config2.workspaces ?? {});
|
|
43339
|
+
const onlyDefaultOrEmpty = workspaceNames.length === 0 || workspaceNames.length === 1 && workspaceNames[0] === DEFAULT_HOME_WORKSPACE_NAME;
|
|
43340
|
+
return Object.keys(state.sessions ?? {}).length === 0 && onlyDefaultOrEmpty && (config2.plugins ?? []).length === 0;
|
|
41983
43341
|
}
|
|
41984
43342
|
async function maybeRunFirstUseOnboarding(input) {
|
|
41985
43343
|
if (!isFirstUse(input.config, input.state))
|
|
@@ -43647,7 +45005,8 @@ function createMcpStdioIdentityResolver(input) {
|
|
|
43647
45005
|
return {
|
|
43648
45006
|
coordinatorSession: resolvedCoordinatorSession,
|
|
43649
45007
|
...sourceHandle ? { sourceHandle } : {},
|
|
43650
|
-
...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {}
|
|
45008
|
+
...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {},
|
|
45009
|
+
...input.internalSessionTools && startup.kind === "existing-session" && !sourceHandle ? { internalSessionTools: true } : {}
|
|
43651
45010
|
};
|
|
43652
45011
|
};
|
|
43653
45012
|
}
|
|
@@ -43692,6 +45051,7 @@ var HELP_LINES = [
|
|
|
43692
45051
|
"weacpx version - 查看版本",
|
|
43693
45052
|
"weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
|
|
43694
45053
|
"weacpx workspace list|add [name] [--raw]|rm <name> - 管理本机工作区(别名:ws)",
|
|
45054
|
+
"weacpx later|lt list|cancel <id> - 管理本机待执行定时任务",
|
|
43695
45055
|
"weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
|
|
43696
45056
|
];
|
|
43697
45057
|
function getUsageText() {
|
|
@@ -43779,6 +45139,17 @@ async function runCli(args, deps = {}) {
|
|
|
43779
45139
|
}
|
|
43780
45140
|
return result;
|
|
43781
45141
|
}
|
|
45142
|
+
case "later":
|
|
45143
|
+
case "lt": {
|
|
45144
|
+
const result = await handleLaterCli(args.slice(1), { print });
|
|
45145
|
+
if (result === null) {
|
|
45146
|
+
for (const line of HELP_LINES) {
|
|
45147
|
+
print(line);
|
|
45148
|
+
}
|
|
45149
|
+
return 1;
|
|
45150
|
+
}
|
|
45151
|
+
return result;
|
|
45152
|
+
}
|
|
43782
45153
|
case "plugin": {
|
|
43783
45154
|
const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
|
|
43784
45155
|
print,
|
|
@@ -44127,6 +45498,48 @@ async function agentRemove(rawName, print) {
|
|
|
44127
45498
|
print(`Agent「${name}」已删除`);
|
|
44128
45499
|
return 0;
|
|
44129
45500
|
}
|
|
45501
|
+
async function handleLaterCli(args, deps) {
|
|
45502
|
+
const subcommand = args[0];
|
|
45503
|
+
switch (subcommand) {
|
|
45504
|
+
case "list":
|
|
45505
|
+
if (args.length !== 1)
|
|
45506
|
+
return null;
|
|
45507
|
+
return await laterList(deps.print);
|
|
45508
|
+
case "cancel":
|
|
45509
|
+
if (args.length !== 2 || !args[1])
|
|
45510
|
+
return null;
|
|
45511
|
+
return await laterCancel(args[1], deps.print);
|
|
45512
|
+
default:
|
|
45513
|
+
return null;
|
|
45514
|
+
}
|
|
45515
|
+
}
|
|
45516
|
+
async function laterList(print) {
|
|
45517
|
+
const scheduled = await createCliScheduledTaskService();
|
|
45518
|
+
print(renderLaterList(scheduled.listPending(), (alias) => toDisplaySessionAlias(alias)));
|
|
45519
|
+
return 0;
|
|
45520
|
+
}
|
|
45521
|
+
async function laterCancel(rawId, print) {
|
|
45522
|
+
const id = normalizeId(rawId);
|
|
45523
|
+
if (id.length === 0) {
|
|
45524
|
+
print("定时任务 ID 不能为空。");
|
|
45525
|
+
return 1;
|
|
45526
|
+
}
|
|
45527
|
+
const scheduled = await createCliScheduledTaskService();
|
|
45528
|
+
const ok = await scheduled.cancelPending(id);
|
|
45529
|
+
if (!ok) {
|
|
45530
|
+
print(`未找到待执行的定时任务 #${id}。`);
|
|
45531
|
+
print("可以用 weacpx later list 查看当前待执行任务。");
|
|
45532
|
+
return 1;
|
|
45533
|
+
}
|
|
45534
|
+
print(`已取消定时任务 #${id}`);
|
|
45535
|
+
return 0;
|
|
45536
|
+
}
|
|
45537
|
+
async function createCliScheduledTaskService() {
|
|
45538
|
+
const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
|
|
45539
|
+
const stateStore = new StateStore(runtimePaths.statePath);
|
|
45540
|
+
const state = await stateStore.load();
|
|
45541
|
+
return new ScheduledTaskService(state, stateStore);
|
|
45542
|
+
}
|
|
44130
45543
|
function resolveConfigPathForCurrentEnv() {
|
|
44131
45544
|
return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
44132
45545
|
}
|
|
@@ -44253,10 +45666,12 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
44253
45666
|
let transport;
|
|
44254
45667
|
let identityResolver;
|
|
44255
45668
|
let availableAgents;
|
|
45669
|
+
let internalSessionTools = false;
|
|
44256
45670
|
try {
|
|
44257
45671
|
const parsedCoordinatorSession = parseCoordinatorSession(args, process.env);
|
|
44258
45672
|
sourceHandle = parseSourceHandle(args, process.env);
|
|
44259
45673
|
const workspace = parseCoordinatorWorkspace(args, process.env);
|
|
45674
|
+
const requestedInternalSessionTools = parseInternalSessionToolsFlag(args, process.env);
|
|
44260
45675
|
endpoint = resolveDefaultOrchestrationEndpoint(process.env, process.platform);
|
|
44261
45676
|
const client = new OrchestrationClient(endpoint);
|
|
44262
45677
|
transport = createOrchestrationTransport(endpoint, { client });
|
|
@@ -44271,10 +45686,12 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
44271
45686
|
workspace,
|
|
44272
45687
|
config: config2,
|
|
44273
45688
|
state,
|
|
44274
|
-
client
|
|
45689
|
+
client,
|
|
45690
|
+
internalSessionTools: requestedInternalSessionTools
|
|
44275
45691
|
});
|
|
44276
45692
|
const eagerIdentity = parsedCoordinatorSession ? await resolveIdentity({ clientName: undefined, listRoots: async () => [] }) : null;
|
|
44277
45693
|
coordinatorSession = eagerIdentity?.coordinatorSession ?? "";
|
|
45694
|
+
internalSessionTools = eagerIdentity?.internalSessionTools ?? false;
|
|
44278
45695
|
identityResolver = eagerIdentity ? undefined : resolveIdentity;
|
|
44279
45696
|
} catch (error2) {
|
|
44280
45697
|
(deps.stderr ?? ((text) => process.stderr.write(text)))(`${error2 instanceof Error ? error2.message : String(error2)}
|
|
@@ -44285,6 +45702,7 @@ async function defaultMcpStdio(args, deps = {}) {
|
|
|
44285
45702
|
transport,
|
|
44286
45703
|
...coordinatorSession ? { coordinatorSession } : {},
|
|
44287
45704
|
...sourceHandle ? { sourceHandle } : {},
|
|
45705
|
+
...internalSessionTools ? { internalSessionTools: true } : {},
|
|
44288
45706
|
...identityResolver ? { resolveIdentity: identityResolver } : {},
|
|
44289
45707
|
...availableAgents ? { availableAgents } : {},
|
|
44290
45708
|
onDiagnostic: (event, context) => {
|