weacpx 0.5.0 → 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/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
  });
@@ -9832,7 +9856,7 @@ function isCoordinatorRouteContextRecord(value) {
9832
9856
  if (!isRecord2(value)) {
9833
9857
  return false;
9834
9858
  }
9835
- 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);
9836
9860
  }
9837
9861
  function isHumanQuestionPackageMessageRecord(value) {
9838
9862
  if (!isRecord2(value)) {
@@ -9985,10 +10009,13 @@ function parseChatContexts(raw, path3) {
9985
10009
  function isScheduledTaskStatus(value) {
9986
10010
  return value === "pending" || value === "triggering" || value === "executed" || value === "cancelled" || value === "missed" || value === "failed";
9987
10011
  }
10012
+ function isOptionalScheduledSessionMode(value) {
10013
+ return value === undefined || value === "temp" || value === "bound";
10014
+ }
9988
10015
  function isScheduledTaskRecord(value) {
9989
10016
  if (!isRecord2(value))
9990
10017
  return false;
9991
- 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);
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);
9992
10019
  }
9993
10020
  function parseScheduledTasks(raw, path3) {
9994
10021
  if (raw === undefined)
@@ -10080,6 +10107,315 @@ var init_state_store = __esm(() => {
10080
10107
  init_types();
10081
10108
  });
10082
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
+
10083
10419
  // src/plugins/plugin-home.ts
10084
10420
  import { mkdir as mkdir6, writeFile as writeFile4 } from "node:fs/promises";
10085
10421
  import { homedir as homedir3 } from "node:os";
@@ -15552,7 +15888,11 @@ async function executeScheduledTurn(input, deps) {
15552
15888
  text: input.promptText,
15553
15889
  ...deliveryContextToken ? { replyContextToken: deliveryContextToken } : {},
15554
15890
  ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
15555
- metadata: { channel: "weixin", scheduledSessionAlias: input.sessionAlias }
15891
+ metadata: {
15892
+ channel: "weixin",
15893
+ scheduledSessionAlias: input.sessionAlias,
15894
+ ...input.sessionDescriptor ? { scheduledSessionDescriptor: input.sessionDescriptor } : {}
15895
+ }
15556
15896
  },
15557
15897
  onReplySegment: sendReplySegment
15558
15898
  });
@@ -15884,78 +16224,6 @@ var init_weixin_channel = __esm(() => {
15884
16224
  init_consumer_lock();
15885
16225
  });
15886
16226
 
15887
- // src/channels/channel-scope.ts
15888
- function registerKnownChannelId(channelId) {
15889
- const normalized = channelId.trim();
15890
- if (!normalized || normalized.includes(":")) {
15891
- throw new Error("channel id must be non-empty and must not contain ':'");
15892
- }
15893
- KNOWN_CHANNEL_IDS.add(normalized);
15894
- }
15895
- function listKnownChannelIds() {
15896
- return Array.from(KNOWN_CHANNEL_IDS);
15897
- }
15898
- function getChannelIdFromChatKey(chatKey) {
15899
- const first = chatKey.split(":", 1)[0];
15900
- return first && KNOWN_CHANNEL_IDS.has(first) ? first : "weixin";
15901
- }
15902
- function toInternalSessionAlias(channelId, displayAlias) {
15903
- const normalized = displayAlias.trim();
15904
- if (normalized.length === 0) {
15905
- throw new Error("display session alias must be non-empty");
15906
- }
15907
- if (normalized.startsWith(`${channelId}:`)) {
15908
- return normalized;
15909
- }
15910
- return `${channelId}:${normalized}`;
15911
- }
15912
- function toDisplaySessionAlias(internalAlias) {
15913
- const [first, ...rest] = internalAlias.split(":");
15914
- if (first && KNOWN_CHANNEL_IDS.has(first) && rest.length > 0) {
15915
- return rest.join(":");
15916
- }
15917
- return internalAlias;
15918
- }
15919
- function isSessionAliasVisibleInChannel(alias, channelId) {
15920
- const [first] = alias.split(":", 1);
15921
- if (first && KNOWN_CHANNEL_IDS.has(first)) {
15922
- return first === channelId;
15923
- }
15924
- return channelId === "weixin";
15925
- }
15926
- function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
15927
- const normalized = displayAlias.trim();
15928
- if (normalized.length === 0) {
15929
- throw new Error("display session alias must be non-empty");
15930
- }
15931
- if (normalized.startsWith(`${channelId}:`)) {
15932
- return normalized;
15933
- }
15934
- const scopedAlias = toInternalSessionAlias(channelId, normalized);
15935
- for (const alias of existingAliases) {
15936
- if (alias === scopedAlias)
15937
- return scopedAlias;
15938
- }
15939
- if (channelId === "weixin") {
15940
- for (const alias of existingAliases) {
15941
- if (alias === normalized)
15942
- return alias;
15943
- }
15944
- }
15945
- return scopedAlias;
15946
- }
15947
- function buildDefaultTransportSession(channelId, displayAlias) {
15948
- const normalized = displayAlias.trim();
15949
- if (normalized.length === 0) {
15950
- throw new Error("display session alias must be non-empty");
15951
- }
15952
- return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
15953
- }
15954
- var KNOWN_CHANNEL_IDS;
15955
- var init_channel_scope = __esm(() => {
15956
- KNOWN_CHANNEL_IDS = new Set(["weixin"]);
15957
- });
15958
-
15959
16227
  // src/plugins/known-plugins.ts
15960
16228
  function listKnownPlugins() {
15961
16229
  return KNOWN_PLUGINS.map((plugin) => ({ ...plugin, channels: [...plugin.channels] }));
@@ -18138,7 +18406,7 @@ async function handleSessionRemove(context, chatKey, alias) {
18138
18406
  return { text: lines.join(`
18139
18407
  `) };
18140
18408
  }
18141
- 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) {
18142
18410
  const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
18143
18411
  if (!session.replyMode)
18144
18412
  session.replyMode = effectiveReplyMode;
@@ -18148,8 +18416,10 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18148
18416
  await context.orchestration.recordCoordinatorRouteContext?.({
18149
18417
  coordinatorSession: session.transportSession,
18150
18418
  chatKey,
18419
+ sessionAlias: session.alias,
18151
18420
  ...replyContextToken ? { replyContextToken } : {},
18152
- ...accountId ? { accountId } : {}
18421
+ ...accountId ? { accountId } : {},
18422
+ ...toCoordinatorRouteChatMetadata(metadata)
18153
18423
  });
18154
18424
  } catch (error2) {
18155
18425
  await context.logger.error("orchestration.coordinator_route_context.record_failed", "failed to record coordinator route context", {
@@ -18158,6 +18428,13 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18158
18428
  chatKey,
18159
18429
  error: error2 instanceof Error ? error2.message : String(error2)
18160
18430
  });
18431
+ return {
18432
+ text: [
18433
+ "无法记录当前会话路由,已取消本次发送。",
18434
+ "请稍后重试;如果问题持续存在,请检查 weacpx 运行日志和 state.json 写入权限。"
18435
+ ].join(`
18436
+ `)
18437
+ };
18161
18438
  }
18162
18439
  }
18163
18440
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
@@ -18183,23 +18460,36 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18183
18460
  throw error2;
18184
18461
  }
18185
18462
  }
18186
- async function handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
18463
+ async function handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18187
18464
  try {
18188
- 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);
18189
18466
  } catch (error2) {
18190
18467
  const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
18191
18468
  if (recovered) {
18192
- 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);
18193
18470
  }
18194
18471
  return context.recovery.renderTransportError(session, error2);
18195
18472
  }
18196
18473
  }
18197
- async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
18474
+ async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18198
18475
  const session = await context.sessions.getCurrentSession(chatKey);
18199
18476
  if (!session) {
18200
18477
  return { text: NO_CURRENT_SESSION_TEXT };
18201
18478
  }
18202
- return await handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
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
+ };
18203
18493
  }
18204
18494
  async function preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId) {
18205
18495
  const orchestration = context.orchestration;
@@ -18962,12 +19252,6 @@ var init_workspace_handler = __esm(() => {
18962
19252
  };
18963
19253
  });
18964
19254
 
18965
- // src/scheduled/scheduled-types.ts
18966
- var LATER_MIN_DELAY_MS = 1e4, LATER_MAX_DELAY_MS, LATER_MESSAGE_PREVIEW_CHARS = 120;
18967
- var init_scheduled_types = __esm(() => {
18968
- LATER_MAX_DELAY_MS = 7 * 24 * 60 * 60 * 1000;
18969
- });
18970
-
18971
19255
  // src/scheduled/parse-later-time.ts
18972
19256
  function parseLaterTime(tokens, now = new Date) {
18973
19257
  if (tokens.length === 0)
@@ -19105,83 +19389,24 @@ var init_parse_later_time = __esm(() => {
19105
19389
  ]);
19106
19390
  });
19107
19391
 
19108
- // src/scheduled/scheduled-render.ts
19109
- function renderLaterHelp() {
19110
- return [
19111
- "定时任务用法:",
19112
- "",
19113
- "创建:",
19114
- "/lt in 2h 检查 CI",
19115
- "/lt 30分钟后 总结进展",
19116
- "/lt tomorrow 09:00 看 PR",
19117
- "/lt 周五 09:00 继续处理",
19118
- "",
19119
- "查看:",
19120
- "/lt list",
19121
- "",
19122
- "取消:",
19123
- "/lt cancel <id>",
19124
- "",
19125
- "说明:",
19126
- "- 只支持一次性任务",
19127
- "- 时间必须在 10 秒之后、7 天之内",
19128
- "- 到点后会把消息发送到创建时绑定的会话",
19129
- "- 触发通知和 agent 回复复用现有频道路由;微信回复额度由现有路由控制",
19130
- "- 不支持延迟执行 / 开头的 weacpx 命令",
19131
- "- 完整时间格式与说明见 docs/later-command.md"
19132
- ].join(`
19133
- `);
19134
- }
19135
- function renderLaterUnsupportedChannel() {
19136
- return [
19137
- "当前频道暂不支持定时任务,未创建任务。",
19138
- "",
19139
- "原因:这个频道还没有实现定时消息投递能力,任务到点后无法把结果发回原聊天。",
19140
- "请切换到支持定时任务的频道后再使用 /lt。"
19141
- ].join(`
19142
- `);
19143
- }
19144
- function renderTaskCreated(task, displaySession) {
19145
- return [
19146
- `已创建定时任务 #${task.id}`,
19147
- `执行时间:${formatLocalDateTime(new Date(task.execute_at))}`,
19148
- `会话:${displaySession}`,
19149
- `内容:${preview(task.message)}`
19150
- ].join(`
19151
- `);
19152
- }
19153
- function renderLaterList(tasks, displaySession) {
19154
- if (tasks.length === 0)
19155
- return "当前没有待执行定时任务。";
19156
- return [
19157
- "待执行定时任务:",
19158
- "",
19159
- ...tasks.flatMap((task) => [
19160
- `#${task.id} ${formatLocalDateTime(new Date(task.execute_at))} 会话:${displaySession(task.session_alias)}`,
19161
- preview(task.message),
19162
- ""
19163
- ])
19164
- ].join(`
19165
- `).trimEnd();
19166
- }
19167
- function preview(text) {
19168
- return text.length <= LATER_MESSAGE_PREVIEW_CHARS ? text : `${text.slice(0, LATER_MESSAGE_PREVIEW_CHARS - 1)}…`;
19169
- }
19170
- function formatLocalDateTime(date4) {
19171
- const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
19172
- const pad = (value) => String(value).padStart(2, "0");
19173
- return `${date4.getFullYear()}-${pad(date4.getMonth() + 1)}-${pad(date4.getDate())} ${weekdays[date4.getDay()]} ${pad(date4.getHours())}:${pad(date4.getMinutes())}`;
19174
- }
19175
- var init_scheduled_render = __esm(() => {
19176
- init_scheduled_types();
19177
- });
19178
-
19179
19392
  // src/commands/handlers/later-handler.ts
19180
19393
  function handleLaterHelp() {
19181
19394
  return { text: renderLaterHelp() };
19182
19395
  }
19183
- async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias, accountId, replyContextToken) {
19184
- if (!currentSessionAlias) {
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) {
19185
19410
  return {
19186
19411
  text: [
19187
19412
  "当前没有会话,无法创建定时任务。",
@@ -19193,11 +19418,11 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias
19193
19418
  `)
19194
19419
  };
19195
19420
  }
19196
- const result = parseLaterTime(tokens);
19421
+ const result = parseLaterTime(rest);
19197
19422
  if (!result.ok) {
19198
19423
  return { text: renderTimeParseError(result.code, result.value) };
19199
19424
  }
19200
- const message = tokens.slice(result.messageStartIndex).join(" ").trim();
19425
+ const message = rest.slice(result.messageStartIndex).join(" ").trim();
19201
19426
  if (message.startsWith("/")) {
19202
19427
  return {
19203
19428
  text: [
@@ -19211,13 +19436,15 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias
19211
19436
  }
19212
19437
  const task = await scheduled.createTask({
19213
19438
  chatKey,
19214
- sessionAlias: currentSessionAlias,
19439
+ sessionAlias: currentSession.alias,
19215
19440
  executeAt: result.executeAt,
19216
19441
  message,
19442
+ sessionMode: mode,
19443
+ ...mode === "temp" ? { agent: currentSession.agent, workspace: currentSession.workspace } : {},
19217
19444
  ...accountId ? { accountId } : {},
19218
19445
  ...replyContextToken ? { replyContextToken } : {}
19219
19446
  });
19220
- return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSessionAlias)) };
19447
+ return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
19221
19448
  }
19222
19449
  function handleLaterList(scheduled) {
19223
19450
  const tasks = scheduled.listPending();
@@ -19265,9 +19492,11 @@ var init_later_handler = __esm(() => {
19265
19492
  laterHelpMetadata = {
19266
19493
  topic: "later",
19267
19494
  aliases: ["lt"],
19268
- summary: "定时任务:延时发送消息到当前会话",
19495
+ summary: "定时任务:到点在临时会话执行(或 --bind 发到当前会话)",
19269
19496
  commands: [
19270
19497
  { usage: "/lt <时间> <消息>", description: "创建定时任务" },
19498
+ { usage: "/lt --bind <时间> <消息>", description: "改为发送到当前会话" },
19499
+ { usage: "/lt --temp <时间> <消息>", description: "强制使用临时会话" },
19271
19500
  { usage: "/lt list", description: "查看待执行定时任务" },
19272
19501
  { usage: "/lt cancel <id>", description: "取消定时任务" }
19273
19502
  ],
@@ -19281,7 +19510,8 @@ var init_later_handler = __esm(() => {
19281
19510
  notes: [
19282
19511
  "只支持一次性任务,不支持重复执行",
19283
19512
  "时间必须在 10 秒之后、7 天之内",
19284
- "到点后会把消息发送到创建时绑定的会话(不随之后 /use 切换而改变)",
19513
+ "默认在为本次任务新建的临时会话里执行,跑完即销毁",
19514
+ "加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
19285
19515
  "/lt list 显示全局待执行任务;群聊中只有群主可取消",
19286
19516
  "不支持延迟执行 / 开头的 weacpx 命令",
19287
19517
  "完整时间格式与说明见 docs/later-command.md"
@@ -19566,6 +19796,15 @@ var init_session_shortcut_handler = __esm(() => {
19566
19796
  function renderTransportError(session, error2) {
19567
19797
  const message = error2 instanceof Error ? error2.message : String(error2);
19568
19798
  if (message.includes("No acpx session found")) {
19799
+ if (session.transient) {
19800
+ return {
19801
+ text: [
19802
+ "定时任务的临时会话启动失败,本次任务未能执行。",
19803
+ "临时会话由系统在执行时自动创建,无需手动操作;如需重排,请用 /lt 重新安排。"
19804
+ ].join(`
19805
+ `)
19806
+ };
19807
+ }
19569
19808
  return {
19570
19809
  text: [
19571
19810
  `当前会话「${session.alias}」暂时不可用。`,
@@ -19642,6 +19881,9 @@ async function tryRecoverMissingSession(ops, session, error2) {
19642
19881
  if (!message.includes("No acpx session found")) {
19643
19882
  return null;
19644
19883
  }
19884
+ if (session.transient) {
19885
+ return null;
19886
+ }
19645
19887
  const transportAgentCommand = await ops.resolveSessionAgentCommand(session);
19646
19888
  if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
19647
19889
  return null;
@@ -20171,7 +20413,7 @@ class CommandRouter {
20171
20413
  return { text: renderLaterUnsupportedChannel() };
20172
20414
  }
20173
20415
  const currentSession = await this.sessions.getCurrentSession(chatKey);
20174
- return await handleLaterCreate(command.tokens, this.scheduled, chatKey, currentSession?.alias ?? null, accountId, replyContextToken);
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);
20175
20417
  }
20176
20418
  case "later.cancel":
20177
20419
  if (!this.scheduled)
@@ -20179,14 +20421,22 @@ class CommandRouter {
20179
20421
  return await handleLaterCancel(command.id, this.scheduled);
20180
20422
  case "prompt": {
20181
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
+ }
20182
20432
  if (metadata?.scheduledSessionAlias) {
20183
20433
  const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
20184
20434
  if (!scheduledSession) {
20185
20435
  throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
20186
20436
  }
20187
- return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
20437
+ return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
20188
20438
  }
20189
- return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
20439
+ return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
20190
20440
  }
20191
20441
  }
20192
20442
  });
@@ -20664,24 +20914,6 @@ var init_console_agent = __esm(() => {
20664
20914
  init_command_list();
20665
20915
  });
20666
20916
 
20667
- // src/orchestration/async-mutex.ts
20668
- class AsyncMutex {
20669
- tail = Promise.resolve();
20670
- async run(critical) {
20671
- const previous = this.tail;
20672
- let release;
20673
- this.tail = new Promise((resolve3) => {
20674
- release = resolve3;
20675
- });
20676
- await previous;
20677
- try {
20678
- return await critical();
20679
- } finally {
20680
- release();
20681
- }
20682
- }
20683
- }
20684
-
20685
20917
  // src/orchestration/orchestration-server.ts
20686
20918
  import { rm as rm7 } from "node:fs/promises";
20687
20919
  import { createConnection as createConnection2, createServer } from "node:net";
@@ -20837,6 +21069,12 @@ class OrchestrationServer {
20837
21069
  reviewId: requireString(params, "reviewId"),
20838
21070
  decision: requireEnum(params, "decision", ["accept", "discard"])
20839
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);
20840
21078
  case "group.new":
20841
21079
  requireOnlyKeys(params, ["coordinatorSession", "title"], "params");
20842
21080
  return await this.handlers.createGroup({
@@ -20847,6 +21085,47 @@ class OrchestrationServer {
20847
21085
  throw new OrchestrationInvalidRequestError(`unsupported orchestration method: ${method}`);
20848
21086
  }
20849
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
+ }
20850
21129
  parseRegisterExternalCoordinatorInput(params) {
20851
21130
  requireOnlyKeys(params, ["coordinatorSession", "workspace", "defaultTargetAgent"], "params");
20852
21131
  const workspace = requireOptionalString(params, "workspace");
@@ -21191,6 +21470,9 @@ var init_orchestration_server = __esm(() => {
21191
21470
  "coordinator.retract_answer",
21192
21471
  "coordinator.request_human_input",
21193
21472
  "coordinator.review_contested_result",
21473
+ "scheduled.create",
21474
+ "scheduled.list",
21475
+ "scheduled.cancel",
21194
21476
  "group.new"
21195
21477
  ]);
21196
21478
  });
@@ -22184,10 +22466,11 @@ class OrchestrationService {
22184
22466
  const state = await this.deps.loadState();
22185
22467
  const now = this.deps.now().toISOString();
22186
22468
  const existing = this.ensureCoordinatorRoutes(state)[input.coordinatorSession];
22469
+ const sameChat = existing?.chatKey === input.chatKey;
22187
22470
  const hasAccountId = input.accountId !== undefined;
22188
22471
  const hasReplyContextToken = input.replyContextToken !== undefined;
22189
22472
  const hasCompleteReplyRoute = hasAccountId && hasReplyContextToken;
22190
- const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken && existing?.chatKey === input.chatKey;
22473
+ const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken && sameChat;
22191
22474
  const replyRoute = hasCompleteReplyRoute ? {
22192
22475
  accountId: input.accountId,
22193
22476
  replyContextToken: input.replyContextToken
@@ -22198,7 +22481,9 @@ class OrchestrationService {
22198
22481
  const route = {
22199
22482
  coordinatorSession: input.coordinatorSession,
22200
22483
  chatKey: input.chatKey,
22484
+ ...input.sessionAlias ? { sessionAlias: input.sessionAlias } : {},
22201
22485
  ...replyRoute ? replyRoute : {},
22486
+ ...buildCoordinatorRouteChatMetadata(input, sameChat ? existing : undefined),
22202
22487
  updatedAt: now
22203
22488
  };
22204
22489
  this.ensureCoordinatorRoutes(state)[input.coordinatorSession] = route;
@@ -24525,6 +24810,22 @@ class OrchestrationService {
24525
24810
  task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
24526
24811
  }
24527
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
+ }
24528
24829
  function isTerminalTaskStatus2(status) {
24529
24830
  return status === "completed" || status === "failed" || status === "cancelled";
24530
24831
  }
@@ -24650,137 +24951,186 @@ var init_scheduled_scheduler = __esm(() => {
24650
24951
  DEFAULT_DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
24651
24952
  });
24652
24953
 
24653
- // src/scheduled/scheduled-service.ts
24654
- class ScheduledTaskService {
24655
- state;
24656
- stateStore;
24657
- now;
24658
- generateId;
24659
- stateMutex;
24660
- claimedInThisSession = new Set;
24661
- constructor(state, stateStore, options) {
24662
- this.state = state;
24663
- this.stateStore = stateStore;
24664
- this.now = options?.now ?? (() => new Date);
24665
- this.generateId = options?.generateId ?? (() => Math.random().toString(36).slice(2, 6));
24666
- this.stateMutex = options?.stateMutex ?? new AsyncMutex;
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`);
24667
24968
  }
24668
- async createTask(input) {
24669
- return await this.mutate(async () => {
24670
- const id = this.nextId();
24671
- const task = {
24672
- id,
24673
- chat_key: input.chatKey,
24674
- session_alias: input.sessionAlias,
24675
- execute_at: input.executeAt.toISOString(),
24676
- message: input.message,
24677
- status: "pending",
24678
- created_at: this.now().toISOString(),
24679
- ...input.accountId ? { account_id: input.accountId } : {},
24680
- ...input.replyContextToken ? { reply_context_token: input.replyContextToken } : {},
24681
- ...input.sourceLabel ? { source_label: input.sourceLabel } : {}
24682
- };
24683
- this.state.scheduled_tasks[id] = task;
24684
- await this.save();
24685
- return task;
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 } : {}
24686
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
+ }
24687
25013
  }
24688
- listPending() {
24689
- return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
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");
24690
25025
  }
24691
- async cancelPending(inputId) {
24692
- return await this.mutate(async () => {
24693
- const id = normalizeId(inputId);
24694
- const task = this.state.scheduled_tasks[id];
24695
- if (!task || task.status !== "pending")
24696
- return false;
24697
- task.status = "cancelled";
24698
- task.cancelled_at = this.now().toISOString();
24699
- await this.save();
24700
- return true;
24701
- });
25026
+ const route = deps.state.orchestration.coordinatorRoutes[coordinatorSession];
25027
+ if (!route) {
25028
+ throw new Error(`no chat route is recorded for coordinator session "${coordinatorSession}"`);
24702
25029
  }
24703
- async markStartupMissed() {
24704
- await this.mutate(async () => {
24705
- const nowMs = this.now().getTime();
24706
- let changed = false;
24707
- for (const task of Object.values(this.state.scheduled_tasks)) {
24708
- if (task.status === "pending" && Date.parse(task.execute_at) < nowMs) {
24709
- task.status = "missed";
24710
- task.missed_at = this.now().toISOString();
24711
- changed = true;
24712
- }
24713
- if (task.status === "triggering" && !this.claimedInThisSession.has(task.id)) {
24714
- task.status = "failed";
24715
- task.failed_at = this.now().toISOString();
24716
- task.last_error = "process stopped while task was triggering";
24717
- changed = true;
24718
- }
24719
- }
24720
- if (changed)
24721
- await this.save();
24722
- });
25030
+ if (route.chatType !== "direct" && route.chatType !== "group") {
25031
+ throw new Error("scheduled_create requires current chat route metadata");
24723
25032
  }
24724
- async claimDueTasks() {
24725
- return await this.mutate(async () => {
24726
- const nowMs = this.now().getTime();
24727
- const due = this.listPending().filter((task) => Date.parse(task.execute_at) <= nowMs);
24728
- if (due.length === 0)
24729
- return [];
24730
- const at = this.now().toISOString();
24731
- for (const task of due) {
24732
- task.status = "triggering";
24733
- task.triggered_at = at;
24734
- this.claimedInThisSession.add(task.id);
24735
- }
24736
- await this.save();
24737
- return due.map((task) => ({ ...task }));
24738
- });
25033
+ if (route.chatType === "group" && route.isOwner !== true) {
25034
+ throw new Error("scheduled_create is owner-only in group chats");
24739
25035
  }
24740
- async markExecuted(id) {
24741
- await this.mutate(async () => {
24742
- const taskId = normalizeId(id);
24743
- const task = this.state.scheduled_tasks[taskId];
24744
- if (!task)
24745
- return;
24746
- task.status = "executed";
24747
- task.executed_at = this.now().toISOString();
24748
- this.claimedInThisSession.delete(taskId);
24749
- await this.save();
24750
- });
25036
+ if (deps.supportsScheduledMessages && !deps.supportsScheduledMessages(route.chatKey)) {
25037
+ throw new Error("current channel does not support scheduled tasks");
24751
25038
  }
24752
- async markFailed(id, error2) {
24753
- await this.mutate(async () => {
24754
- const taskId = normalizeId(id);
24755
- const task = this.state.scheduled_tasks[taskId];
24756
- if (!task)
24757
- return;
24758
- task.status = "failed";
24759
- task.failed_at = this.now().toISOString();
24760
- task.last_error = error2 instanceof Error ? error2.message : String(error2);
24761
- this.claimedInThisSession.delete(taskId);
24762
- await this.save();
24763
- });
25039
+ const message = input.message.trim();
25040
+ if (message.length === 0) {
25041
+ throw new Error("message must be a non-empty string");
24764
25042
  }
24765
- nextId() {
24766
- for (let attempt = 0;attempt < 20; attempt += 1) {
24767
- const id = normalizeId(this.generateId()).replace(/[^0-9a-z]/g, "").slice(0, 6);
24768
- if (id.length >= 4 && !this.state.scheduled_tasks[id])
24769
- return id;
24770
- }
24771
- throw new Error("failed to generate unique scheduled task id");
25043
+ if (message.startsWith("/")) {
25044
+ throw new Error("scheduled_create does not support slash-prefixed weacpx commands");
24772
25045
  }
24773
- async mutate(critical) {
24774
- return await this.stateMutex.run(critical);
25046
+ if (!route.sessionAlias) {
25047
+ throw new Error("scheduled_create requires current session route metadata");
24775
25048
  }
24776
- async save() {
24777
- await this.stateStore.save(this.state);
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`);
24778
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
+ });
24779
25069
  }
24780
- function normalizeId(input) {
24781
- return input.trim().replace(/^#/, "").toLowerCase();
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;
24782
25083
  }
24783
- var init_scheduled_service = () => {};
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
+ });
24784
25134
 
24785
25135
  // src/sessions/session-service.ts
24786
25136
  class SessionService {
@@ -26417,7 +26767,7 @@ function buildWeacpxMcpServerSpec(input) {
26417
26767
  "mcp-stdio",
26418
26768
  "--coordinator-session",
26419
26769
  input.coordinatorSession,
26420
- ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : []
26770
+ ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
26421
26771
  ]
26422
26772
  };
26423
26773
  }
@@ -27867,32 +28217,32 @@ async function buildApp(paths, deps = {}) {
27867
28217
  }
27868
28218
  const progressHeartbeatInterval = startProgressHeartbeat(orchestration, config2, logger2, deps.channel ?? null);
27869
28219
  const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
27870
- const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration);
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
+ });
27871
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);
27872
28232
  const agent = new ConsoleAgent(router, logger2);
27873
28233
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
27874
- dispatchTask: async (task, abortSignal) => {
27875
- const session = await sessions.getSession(task.session_alias);
27876
- if (!session) {
27877
- throw new Error(`session "${task.session_alias}" not found for scheduled task`);
27878
- }
27879
- const noticeText = `执行定时任务 #${task.id}
27880
- 会话:${toDisplaySessionAlias(task.session_alias)}
27881
- 内容:${preview(task.message)}`;
27882
- if (!deps.channel?.sendScheduledMessage) {
27883
- throw new Error("no channel runtime available for scheduled task dispatch");
27884
- }
27885
- await deps.channel.sendScheduledMessage({
27886
- chatKey: task.chat_key,
27887
- taskId: task.id,
27888
- sessionAlias: task.session_alias,
27889
- noticeText,
27890
- promptText: task.message,
27891
- abortSignal,
27892
- ...task.account_id ? { accountId: task.account_id } : {},
27893
- ...task.reply_context_token ? { replyContextToken: task.reply_context_token } : {}
27894
- });
27895
- },
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
+ }),
27896
28246
  logger: logger2
27897
28247
  });
27898
28248
  return {
@@ -28065,8 +28415,9 @@ var init_main = __esm(async () => {
28065
28415
  init_build_coordinator_prompt();
28066
28416
  init_scheduled_scheduler();
28067
28417
  init_scheduled_service();
28068
- init_scheduled_render();
28069
- init_channel_scope();
28418
+ init_scheduled_dispatch();
28419
+ init_scheduled_route_create();
28420
+ init_scheduled_route_manage();
28070
28421
  init_session_service();
28071
28422
  init_state_store();
28072
28423
  init_run_console();
@@ -41598,12 +41949,13 @@ var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
41598
41949
  var orderSchema = exports_external.enum(["asc", "desc"]);
41599
41950
  var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
41600
41951
  var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
41952
+ var scheduledModeSchema = exports_external.enum(["temp", "bound"]);
41601
41953
  var taskQuestionSchema = exports_external.object({
41602
41954
  taskId: exports_external.string().min(1),
41603
41955
  questionId: exports_external.string().min(1)
41604
41956
  }).strict();
41605
41957
  function buildWeacpxMcpToolRegistry(input) {
41606
- const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, availableAgents } = input;
41958
+ const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, internalSessionTools, availableAgents } = input;
41607
41959
  const tools = [
41608
41960
  {
41609
41961
  name: "delegate_request",
@@ -41821,6 +42173,63 @@ function buildWeacpxMcpToolRegistry(input) {
41821
42173
  })
41822
42174
  }
41823
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
+ }
41824
42233
  if (isExternalCoordinator) {
41825
42234
  const externalCoordinatorIncompatibleTools = new Set([
41826
42235
  "coordinator_request_human_input"
@@ -41900,6 +42309,19 @@ function renderTaskList(tasks) {
41900
42309
  return ["Tasks for the current coordinator:", ...tasks.map((task) => renderTaskListItem(task))].join(`
41901
42310
  `);
41902
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
+ }
41903
42325
  function renderTaskListItem(task) {
41904
42326
  const role = task.role ? ` / ${task.role}` : "";
41905
42327
  const group = task.groupId ? `; group: ${task.groupId}` : "";
@@ -42070,6 +42492,15 @@ class OrchestrationClient {
42070
42492
  async createGroup(input) {
42071
42493
  return await this.request("group.new", input);
42072
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
+ }
42073
42504
  async request(method, params, timeoutMs = this.timeoutMs) {
42074
42505
  const id = this.createId();
42075
42506
  return await new Promise((resolve, reject) => {
@@ -42182,7 +42613,25 @@ function createOrchestrationTransport(endpoint, deps = {}) {
42182
42613
  },
42183
42614
  coordinatorAnswerQuestion: async (input) => await client.coordinatorAnswerQuestion(input),
42184
42615
  coordinatorRequestHumanInput: async (input) => await client.coordinatorRequestHumanInput(input),
42185
- 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
+ }
42186
42635
  };
42187
42636
  }
42188
42637
 
@@ -42238,6 +42687,7 @@ function createWeacpxMcpServer(options) {
42238
42687
  coordinatorSession: identity.coordinatorSession,
42239
42688
  ...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {},
42240
42689
  ...identity.isExternalCoordinator ? { isExternalCoordinator: true } : {},
42690
+ ...identity.internalSessionTools ? { internalSessionTools: true } : {},
42241
42691
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
42242
42692
  });
42243
42693
  return toolState;
@@ -42673,7 +43123,8 @@ async function resolveMcpIdentity(server, options) {
42673
43123
  return {
42674
43124
  coordinatorSession: options.coordinatorSession,
42675
43125
  ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
42676
- ...options.isExternalCoordinator ? { isExternalCoordinator: true } : {}
43126
+ ...options.isExternalCoordinator ? { isExternalCoordinator: true } : {},
43127
+ ...options.internalSessionTools ? { internalSessionTools: true } : {}
42677
43128
  };
42678
43129
  }
42679
43130
  throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
@@ -42762,6 +43213,7 @@ async function runWeacpxMcpServer(options) {
42762
43213
  transport,
42763
43214
  ...options.coordinatorSession ? { coordinatorSession: options.coordinatorSession } : {},
42764
43215
  ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
43216
+ ...options.internalSessionTools ? { internalSessionTools: true } : {},
42765
43217
  ...options.resolveIdentity ? { resolveIdentity: options.resolveIdentity } : {},
42766
43218
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
42767
43219
  });
@@ -42856,6 +43308,11 @@ function parseCoordinatorSession(args, env = process.env) {
42856
43308
  });
42857
43309
  }
42858
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
+
42859
43316
  // src/mcp/parse-source-handle.ts
42860
43317
  function parseSourceHandle(args, env = process.env) {
42861
43318
  return parseStringFlag(args, env, {
@@ -42868,13 +43325,19 @@ function parseSourceHandle(args, env = process.env) {
42868
43325
  init_workspace_path();
42869
43326
  init_workspace_name();
42870
43327
  init_state_store();
43328
+ init_channel_scope();
43329
+ init_scheduled_render();
43330
+ init_scheduled_service();
42871
43331
 
42872
43332
  // src/onboarding.ts
42873
43333
  init_workspace_path();
42874
43334
  init_workspace_name();
43335
+ init_default_workspace();
42875
43336
  init_agent_templates();
42876
43337
  function isFirstUse(config2, state) {
42877
- return Object.keys(state.sessions ?? {}).length === 0 && Object.keys(config2.workspaces ?? {}).length === 0 && (config2.plugins ?? []).length === 0;
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;
42878
43341
  }
42879
43342
  async function maybeRunFirstUseOnboarding(input) {
42880
43343
  if (!isFirstUse(input.config, input.state))
@@ -44542,7 +45005,8 @@ function createMcpStdioIdentityResolver(input) {
44542
45005
  return {
44543
45006
  coordinatorSession: resolvedCoordinatorSession,
44544
45007
  ...sourceHandle ? { sourceHandle } : {},
44545
- ...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {}
45008
+ ...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {},
45009
+ ...input.internalSessionTools && startup.kind === "existing-session" && !sourceHandle ? { internalSessionTools: true } : {}
44546
45010
  };
44547
45011
  };
44548
45012
  }
@@ -44587,6 +45051,7 @@ var HELP_LINES = [
44587
45051
  "weacpx version - 查看版本",
44588
45052
  "weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
44589
45053
  "weacpx workspace list|add [name] [--raw]|rm <name> - 管理本机工作区(别名:ws)",
45054
+ "weacpx later|lt list|cancel <id> - 管理本机待执行定时任务",
44590
45055
  "weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
44591
45056
  ];
44592
45057
  function getUsageText() {
@@ -44674,6 +45139,17 @@ async function runCli(args, deps = {}) {
44674
45139
  }
44675
45140
  return result;
44676
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
+ }
44677
45153
  case "plugin": {
44678
45154
  const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
44679
45155
  print,
@@ -45022,6 +45498,48 @@ async function agentRemove(rawName, print) {
45022
45498
  print(`Agent「${name}」已删除`);
45023
45499
  return 0;
45024
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
+ }
45025
45543
  function resolveConfigPathForCurrentEnv() {
45026
45544
  return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
45027
45545
  }
@@ -45148,10 +45666,12 @@ async function defaultMcpStdio(args, deps = {}) {
45148
45666
  let transport;
45149
45667
  let identityResolver;
45150
45668
  let availableAgents;
45669
+ let internalSessionTools = false;
45151
45670
  try {
45152
45671
  const parsedCoordinatorSession = parseCoordinatorSession(args, process.env);
45153
45672
  sourceHandle = parseSourceHandle(args, process.env);
45154
45673
  const workspace = parseCoordinatorWorkspace(args, process.env);
45674
+ const requestedInternalSessionTools = parseInternalSessionToolsFlag(args, process.env);
45155
45675
  endpoint = resolveDefaultOrchestrationEndpoint(process.env, process.platform);
45156
45676
  const client = new OrchestrationClient(endpoint);
45157
45677
  transport = createOrchestrationTransport(endpoint, { client });
@@ -45166,10 +45686,12 @@ async function defaultMcpStdio(args, deps = {}) {
45166
45686
  workspace,
45167
45687
  config: config2,
45168
45688
  state,
45169
- client
45689
+ client,
45690
+ internalSessionTools: requestedInternalSessionTools
45170
45691
  });
45171
45692
  const eagerIdentity = parsedCoordinatorSession ? await resolveIdentity({ clientName: undefined, listRoots: async () => [] }) : null;
45172
45693
  coordinatorSession = eagerIdentity?.coordinatorSession ?? "";
45694
+ internalSessionTools = eagerIdentity?.internalSessionTools ?? false;
45173
45695
  identityResolver = eagerIdentity ? undefined : resolveIdentity;
45174
45696
  } catch (error2) {
45175
45697
  (deps.stderr ?? ((text) => process.stderr.write(text)))(`${error2 instanceof Error ? error2.message : String(error2)}
@@ -45180,6 +45702,7 @@ async function defaultMcpStdio(args, deps = {}) {
45180
45702
  transport,
45181
45703
  ...coordinatorSession ? { coordinatorSession } : {},
45182
45704
  ...sourceHandle ? { sourceHandle } : {},
45705
+ ...internalSessionTools ? { internalSessionTools: true } : {},
45183
45706
  ...identityResolver ? { resolveIdentity: identityResolver } : {},
45184
45707
  ...availableAgents ? { availableAgents } : {},
45185
45708
  onDiagnostic: (event, context) => {