weacpx 0.5.0 → 0.5.2

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";
@@ -14617,6 +14953,12 @@ async function handleWeixinMessageTurn(full, deps) {
14617
14953
  text: requestText,
14618
14954
  ...media.length > 0 ? { media } : {},
14619
14955
  replyContextToken: contextToken,
14956
+ metadata: {
14957
+ channel: "weixin",
14958
+ chatType: full.group_id ? "group" : "direct",
14959
+ ...full.from_user_id ? { senderId: full.from_user_id } : {},
14960
+ ...full.group_id ? { groupId: full.group_id } : {}
14961
+ },
14620
14962
  perfSpan
14621
14963
  };
14622
14964
  try {
@@ -15552,7 +15894,11 @@ async function executeScheduledTurn(input, deps) {
15552
15894
  text: input.promptText,
15553
15895
  ...deliveryContextToken ? { replyContextToken: deliveryContextToken } : {},
15554
15896
  ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
15555
- metadata: { channel: "weixin", scheduledSessionAlias: input.sessionAlias }
15897
+ metadata: {
15898
+ channel: "weixin",
15899
+ scheduledSessionAlias: input.sessionAlias,
15900
+ ...input.sessionDescriptor ? { scheduledSessionDescriptor: input.sessionDescriptor } : {}
15901
+ }
15556
15902
  },
15557
15903
  onReplySegment: sendReplySegment
15558
15904
  });
@@ -15884,78 +16230,6 @@ var init_weixin_channel = __esm(() => {
15884
16230
  init_consumer_lock();
15885
16231
  });
15886
16232
 
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
16233
  // src/plugins/known-plugins.ts
15960
16234
  function listKnownPlugins() {
15961
16235
  return KNOWN_PLUGINS.map((plugin) => ({ ...plugin, channels: [...plugin.channels] }));
@@ -18138,7 +18412,7 @@ async function handleSessionRemove(context, chatKey, alias) {
18138
18412
  return { text: lines.join(`
18139
18413
  `) };
18140
18414
  }
18141
- async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
18415
+ async function promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18142
18416
  const effectiveReplyMode = session.replyMode ?? context.config?.channel.replyMode ?? "verbose";
18143
18417
  if (!session.replyMode)
18144
18418
  session.replyMode = effectiveReplyMode;
@@ -18148,8 +18422,10 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18148
18422
  await context.orchestration.recordCoordinatorRouteContext?.({
18149
18423
  coordinatorSession: session.transportSession,
18150
18424
  chatKey,
18425
+ sessionAlias: session.alias,
18151
18426
  ...replyContextToken ? { replyContextToken } : {},
18152
- ...accountId ? { accountId } : {}
18427
+ ...accountId ? { accountId } : {},
18428
+ ...toCoordinatorRouteChatMetadata(metadata)
18153
18429
  });
18154
18430
  } catch (error2) {
18155
18431
  await context.logger.error("orchestration.coordinator_route_context.record_failed", "failed to record coordinator route context", {
@@ -18158,6 +18434,13 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18158
18434
  chatKey,
18159
18435
  error: error2 instanceof Error ? error2.message : String(error2)
18160
18436
  });
18437
+ return {
18438
+ text: [
18439
+ "无法记录当前会话路由,已取消本次发送。",
18440
+ "请稍后重试;如果问题持续存在,请检查 weacpx 运行日志和 state.json 写入权限。"
18441
+ ].join(`
18442
+ `)
18443
+ };
18161
18444
  }
18162
18445
  }
18163
18446
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId);
@@ -18183,23 +18466,36 @@ async function promptWithSession(context, session, chatKey, text, reply, replyCo
18183
18466
  throw error2;
18184
18467
  }
18185
18468
  }
18186
- async function handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
18469
+ async function handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18187
18470
  try {
18188
- return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
18471
+ return await promptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
18189
18472
  } catch (error2) {
18190
18473
  const recovered = await context.recovery.tryRecoverMissingSession(session, error2);
18191
18474
  if (recovered) {
18192
- return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
18475
+ return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
18193
18476
  }
18194
18477
  return context.recovery.renderTransportError(session, error2);
18195
18478
  }
18196
18479
  }
18197
- async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan) {
18480
+ async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18198
18481
  const session = await context.sessions.getCurrentSession(chatKey);
18199
18482
  if (!session) {
18200
18483
  return { text: NO_CURRENT_SESSION_TEXT };
18201
18484
  }
18202
- return await handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
18485
+ return await handlePromptWithSession(context, session, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
18486
+ }
18487
+ function toCoordinatorRouteChatMetadata(metadata) {
18488
+ if (!metadata) {
18489
+ return {};
18490
+ }
18491
+ return {
18492
+ ...metadata.channel ? { channel: metadata.channel } : {},
18493
+ ...metadata.chatType ? { chatType: metadata.chatType } : {},
18494
+ ...metadata.senderId ? { senderId: metadata.senderId } : {},
18495
+ ...metadata.senderName ? { senderName: metadata.senderName } : {},
18496
+ ...metadata.groupId ? { groupId: metadata.groupId } : {},
18497
+ ...metadata.isOwner !== undefined ? { isOwner: metadata.isOwner } : {}
18498
+ };
18203
18499
  }
18204
18500
  async function preparePromptWithFallback(context, session, chatKey, text, replyContextToken, accountId) {
18205
18501
  const orchestration = context.orchestration;
@@ -18962,12 +19258,6 @@ var init_workspace_handler = __esm(() => {
18962
19258
  };
18963
19259
  });
18964
19260
 
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
19261
  // src/scheduled/parse-later-time.ts
18972
19262
  function parseLaterTime(tokens, now = new Date) {
18973
19263
  if (tokens.length === 0)
@@ -19105,83 +19395,24 @@ var init_parse_later_time = __esm(() => {
19105
19395
  ]);
19106
19396
  });
19107
19397
 
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
19398
  // src/commands/handlers/later-handler.ts
19180
19399
  function handleLaterHelp() {
19181
19400
  return { text: renderLaterHelp() };
19182
19401
  }
19183
- async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias, accountId, replyContextToken) {
19184
- if (!currentSessionAlias) {
19402
+ async function handleLaterCreate(tokens, scheduled, chatKey, currentSession, defaultMode, accountId, replyContextToken) {
19403
+ let rest = tokens;
19404
+ const seenFlags = new Set;
19405
+ let flagMode;
19406
+ while (rest.length > 0 && (rest[0] === "--bind" || rest[0] === "--temp")) {
19407
+ seenFlags.add(rest[0]);
19408
+ flagMode = rest[0] === "--bind" ? "bound" : "temp";
19409
+ rest = rest.slice(1);
19410
+ }
19411
+ if (seenFlags.size > 1) {
19412
+ return { text: "定时任务的 --bind 与 --temp 不能同时使用。" };
19413
+ }
19414
+ const mode = flagMode ?? defaultMode;
19415
+ if (!currentSession) {
19185
19416
  return {
19186
19417
  text: [
19187
19418
  "当前没有会话,无法创建定时任务。",
@@ -19193,11 +19424,11 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias
19193
19424
  `)
19194
19425
  };
19195
19426
  }
19196
- const result = parseLaterTime(tokens);
19427
+ const result = parseLaterTime(rest);
19197
19428
  if (!result.ok) {
19198
19429
  return { text: renderTimeParseError(result.code, result.value) };
19199
19430
  }
19200
- const message = tokens.slice(result.messageStartIndex).join(" ").trim();
19431
+ const message = rest.slice(result.messageStartIndex).join(" ").trim();
19201
19432
  if (message.startsWith("/")) {
19202
19433
  return {
19203
19434
  text: [
@@ -19211,13 +19442,15 @@ async function handleLaterCreate(tokens, scheduled, chatKey, currentSessionAlias
19211
19442
  }
19212
19443
  const task = await scheduled.createTask({
19213
19444
  chatKey,
19214
- sessionAlias: currentSessionAlias,
19445
+ sessionAlias: currentSession.alias,
19215
19446
  executeAt: result.executeAt,
19216
19447
  message,
19448
+ sessionMode: mode,
19449
+ ...mode === "temp" ? { agent: currentSession.agent, workspace: currentSession.workspace } : {},
19217
19450
  ...accountId ? { accountId } : {},
19218
19451
  ...replyContextToken ? { replyContextToken } : {}
19219
19452
  });
19220
- return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSessionAlias)) };
19453
+ return { text: renderTaskCreated(task, toDisplaySessionAlias(currentSession.alias)) };
19221
19454
  }
19222
19455
  function handleLaterList(scheduled) {
19223
19456
  const tasks = scheduled.listPending();
@@ -19265,9 +19498,11 @@ var init_later_handler = __esm(() => {
19265
19498
  laterHelpMetadata = {
19266
19499
  topic: "later",
19267
19500
  aliases: ["lt"],
19268
- summary: "定时任务:延时发送消息到当前会话",
19501
+ summary: "定时任务:到点在临时会话执行(或 --bind 发到当前会话)",
19269
19502
  commands: [
19270
19503
  { usage: "/lt <时间> <消息>", description: "创建定时任务" },
19504
+ { usage: "/lt --bind <时间> <消息>", description: "改为发送到当前会话" },
19505
+ { usage: "/lt --temp <时间> <消息>", description: "强制使用临时会话" },
19271
19506
  { usage: "/lt list", description: "查看待执行定时任务" },
19272
19507
  { usage: "/lt cancel <id>", description: "取消定时任务" }
19273
19508
  ],
@@ -19281,7 +19516,8 @@ var init_later_handler = __esm(() => {
19281
19516
  notes: [
19282
19517
  "只支持一次性任务,不支持重复执行",
19283
19518
  "时间必须在 10 秒之后、7 天之内",
19284
- "到点后会把消息发送到创建时绑定的会话(不随之后 /use 切换而改变)",
19519
+ "默认在为本次任务新建的临时会话里执行,跑完即销毁",
19520
+ "加 --bind 改为发送到创建时绑定的当前会话(默认模式可用 later.defaultMode 配置)",
19285
19521
  "/lt list 显示全局待执行任务;群聊中只有群主可取消",
19286
19522
  "不支持延迟执行 / 开头的 weacpx 命令",
19287
19523
  "完整时间格式与说明见 docs/later-command.md"
@@ -19566,6 +19802,15 @@ var init_session_shortcut_handler = __esm(() => {
19566
19802
  function renderTransportError(session, error2) {
19567
19803
  const message = error2 instanceof Error ? error2.message : String(error2);
19568
19804
  if (message.includes("No acpx session found")) {
19805
+ if (session.transient) {
19806
+ return {
19807
+ text: [
19808
+ "定时任务的临时会话启动失败,本次任务未能执行。",
19809
+ "临时会话由系统在执行时自动创建,无需手动操作;如需重排,请用 /lt 重新安排。"
19810
+ ].join(`
19811
+ `)
19812
+ };
19813
+ }
19569
19814
  return {
19570
19815
  text: [
19571
19816
  `当前会话「${session.alias}」暂时不可用。`,
@@ -19642,6 +19887,9 @@ async function tryRecoverMissingSession(ops, session, error2) {
19642
19887
  if (!message.includes("No acpx session found")) {
19643
19888
  return null;
19644
19889
  }
19890
+ if (session.transient) {
19891
+ return null;
19892
+ }
19645
19893
  const transportAgentCommand = await ops.resolveSessionAgentCommand(session);
19646
19894
  if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
19647
19895
  return null;
@@ -20171,7 +20419,7 @@ class CommandRouter {
20171
20419
  return { text: renderLaterUnsupportedChannel() };
20172
20420
  }
20173
20421
  const currentSession = await this.sessions.getCurrentSession(chatKey);
20174
- return await handleLaterCreate(command.tokens, this.scheduled, chatKey, currentSession?.alias ?? null, accountId, replyContextToken);
20422
+ 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
20423
  }
20176
20424
  case "later.cancel":
20177
20425
  if (!this.scheduled)
@@ -20179,14 +20427,22 @@ class CommandRouter {
20179
20427
  return await handleLaterCancel(command.id, this.scheduled);
20180
20428
  case "prompt": {
20181
20429
  const sessionContext = this.createSessionHandlerContext(undefined, perfSpan);
20430
+ if (metadata?.scheduledSessionDescriptor) {
20431
+ const descriptor = metadata.scheduledSessionDescriptor;
20432
+ const transientSession = {
20433
+ ...this.sessions.resolveSession(descriptor.alias, descriptor.agent, descriptor.workspace, descriptor.transportSession),
20434
+ transient: true
20435
+ };
20436
+ return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
20437
+ }
20182
20438
  if (metadata?.scheduledSessionAlias) {
20183
20439
  const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
20184
20440
  if (!scheduledSession) {
20185
20441
  throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
20186
20442
  }
20187
- return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
20443
+ return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
20188
20444
  }
20189
- return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan);
20445
+ return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
20190
20446
  }
20191
20447
  }
20192
20448
  });
@@ -20664,24 +20920,6 @@ var init_console_agent = __esm(() => {
20664
20920
  init_command_list();
20665
20921
  });
20666
20922
 
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
20923
  // src/orchestration/orchestration-server.ts
20686
20924
  import { rm as rm7 } from "node:fs/promises";
20687
20925
  import { createConnection as createConnection2, createServer } from "node:net";
@@ -20837,6 +21075,12 @@ class OrchestrationServer {
20837
21075
  reviewId: requireString(params, "reviewId"),
20838
21076
  decision: requireEnum(params, "decision", ["accept", "discard"])
20839
21077
  });
21078
+ case "scheduled.create":
21079
+ return await this.dispatchScheduledCreate(params);
21080
+ case "scheduled.list":
21081
+ return await this.dispatchScheduledList(params);
21082
+ case "scheduled.cancel":
21083
+ return await this.dispatchScheduledCancel(params);
20840
21084
  case "group.new":
20841
21085
  requireOnlyKeys(params, ["coordinatorSession", "title"], "params");
20842
21086
  return await this.handlers.createGroup({
@@ -20847,6 +21091,47 @@ class OrchestrationServer {
20847
21091
  throw new OrchestrationInvalidRequestError(`unsupported orchestration method: ${method}`);
20848
21092
  }
20849
21093
  }
21094
+ async dispatchScheduledCreate(params) {
21095
+ const input = this.parseScheduledCreateInput(params);
21096
+ const handler = this.deps.createScheduledTaskFromRoute ?? this.handlers.createScheduledTaskFromRoute;
21097
+ if (!handler) {
21098
+ throw new Error("scheduled task creation is not configured");
21099
+ }
21100
+ return await handler(input);
21101
+ }
21102
+ parseScheduledCreateInput(params) {
21103
+ requireOnlyKeys(params, ["coordinatorSession", "timeText", "message", "mode"], "params");
21104
+ const mode = requireOptionalEnum(params, "mode", ["temp", "bound"]);
21105
+ return {
21106
+ coordinatorSession: requireString(params, "coordinatorSession"),
21107
+ timeText: requireString(params, "timeText"),
21108
+ message: requireString(params, "message"),
21109
+ ...mode !== undefined ? { mode } : {}
21110
+ };
21111
+ }
21112
+ async dispatchScheduledList(params) {
21113
+ requireOnlyKeys(params, ["coordinatorSession"], "params");
21114
+ const input = {
21115
+ coordinatorSession: requireString(params, "coordinatorSession")
21116
+ };
21117
+ const handler = this.deps.listScheduledTasksFromRoute ?? this.handlers.listScheduledTasksFromRoute;
21118
+ if (!handler) {
21119
+ throw new Error("scheduled task listing is not configured");
21120
+ }
21121
+ return await handler(input);
21122
+ }
21123
+ async dispatchScheduledCancel(params) {
21124
+ requireOnlyKeys(params, ["coordinatorSession", "id"], "params");
21125
+ const input = {
21126
+ coordinatorSession: requireString(params, "coordinatorSession"),
21127
+ id: requireString(params, "id")
21128
+ };
21129
+ const handler = this.deps.cancelScheduledTaskFromRoute ?? this.handlers.cancelScheduledTaskFromRoute;
21130
+ if (!handler) {
21131
+ throw new Error("scheduled task cancellation is not configured");
21132
+ }
21133
+ return await handler(input);
21134
+ }
20850
21135
  parseRegisterExternalCoordinatorInput(params) {
20851
21136
  requireOnlyKeys(params, ["coordinatorSession", "workspace", "defaultTargetAgent"], "params");
20852
21137
  const workspace = requireOptionalString(params, "workspace");
@@ -21191,6 +21476,9 @@ var init_orchestration_server = __esm(() => {
21191
21476
  "coordinator.retract_answer",
21192
21477
  "coordinator.request_human_input",
21193
21478
  "coordinator.review_contested_result",
21479
+ "scheduled.create",
21480
+ "scheduled.list",
21481
+ "scheduled.cancel",
21194
21482
  "group.new"
21195
21483
  ]);
21196
21484
  });
@@ -22184,10 +22472,11 @@ class OrchestrationService {
22184
22472
  const state = await this.deps.loadState();
22185
22473
  const now = this.deps.now().toISOString();
22186
22474
  const existing = this.ensureCoordinatorRoutes(state)[input.coordinatorSession];
22475
+ const sameChat = existing?.chatKey === input.chatKey;
22187
22476
  const hasAccountId = input.accountId !== undefined;
22188
22477
  const hasReplyContextToken = input.replyContextToken !== undefined;
22189
22478
  const hasCompleteReplyRoute = hasAccountId && hasReplyContextToken;
22190
- const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken && existing?.chatKey === input.chatKey;
22479
+ const shouldPreserveExistingReplyRoute = !hasAccountId && !hasReplyContextToken && sameChat;
22191
22480
  const replyRoute = hasCompleteReplyRoute ? {
22192
22481
  accountId: input.accountId,
22193
22482
  replyContextToken: input.replyContextToken
@@ -22198,7 +22487,9 @@ class OrchestrationService {
22198
22487
  const route = {
22199
22488
  coordinatorSession: input.coordinatorSession,
22200
22489
  chatKey: input.chatKey,
22490
+ ...input.sessionAlias ? { sessionAlias: input.sessionAlias } : {},
22201
22491
  ...replyRoute ? replyRoute : {},
22492
+ ...buildCoordinatorRouteChatMetadata(input, sameChat ? existing : undefined),
22202
22493
  updatedAt: now
22203
22494
  };
22204
22495
  this.ensureCoordinatorRoutes(state)[input.coordinatorSession] = route;
@@ -24525,6 +24816,22 @@ class OrchestrationService {
24525
24816
  task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
24526
24817
  }
24527
24818
  }
24819
+ function buildCoordinatorRouteChatMetadata(input, existing) {
24820
+ const channel = input.channel ?? existing?.channel;
24821
+ const chatType = input.chatType ?? existing?.chatType;
24822
+ const senderId = input.senderId ?? existing?.senderId;
24823
+ const senderName = input.senderName ?? existing?.senderName;
24824
+ const groupId = input.groupId ?? existing?.groupId;
24825
+ const isOwner = input.isOwner ?? existing?.isOwner;
24826
+ return {
24827
+ ...channel !== undefined ? { channel } : {},
24828
+ ...chatType !== undefined ? { chatType } : {},
24829
+ ...senderId !== undefined ? { senderId } : {},
24830
+ ...senderName !== undefined ? { senderName } : {},
24831
+ ...groupId !== undefined ? { groupId } : {},
24832
+ ...isOwner !== undefined ? { isOwner } : {}
24833
+ };
24834
+ }
24528
24835
  function isTerminalTaskStatus2(status) {
24529
24836
  return status === "completed" || status === "failed" || status === "cancelled";
24530
24837
  }
@@ -24650,137 +24957,186 @@ var init_scheduled_scheduler = __esm(() => {
24650
24957
  DEFAULT_DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
24651
24958
  });
24652
24959
 
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;
24960
+ // src/scheduled/scheduled-dispatch.ts
24961
+ function buildScheduledDispatchTask(deps) {
24962
+ return async (task, abortSignal) => {
24963
+ if (task.session_mode === "temp") {
24964
+ await dispatchTemp(task, abortSignal, deps);
24965
+ return;
24966
+ }
24967
+ await dispatchBound(task, abortSignal, deps);
24968
+ };
24969
+ }
24970
+ async function dispatchBound(task, abortSignal, deps) {
24971
+ const session = await deps.getSession(task.session_alias);
24972
+ if (!session) {
24973
+ throw new Error(`session "${task.session_alias}" not found for scheduled task`);
24667
24974
  }
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;
24975
+ const noticeText = `执行定时任务 #${task.id}
24976
+ 会话:${toDisplaySessionAlias(task.session_alias)}
24977
+ 内容:${preview(task.message)}`;
24978
+ await deps.sendScheduledMessage({
24979
+ chatKey: task.chat_key,
24980
+ taskId: task.id,
24981
+ sessionAlias: task.session_alias,
24982
+ noticeText,
24983
+ promptText: task.message,
24984
+ abortSignal,
24985
+ ...task.account_id ? { accountId: task.account_id } : {},
24986
+ ...task.reply_context_token ? { replyContextToken: task.reply_context_token } : {}
24987
+ });
24988
+ }
24989
+ async function dispatchTemp(task, abortSignal, deps) {
24990
+ if (!task.agent || !task.workspace) {
24991
+ throw new Error(`temp scheduled task #${task.id} is missing its agent/workspace snapshot`);
24992
+ }
24993
+ const alias = `later-${task.id}`;
24994
+ const transportSession = `${task.workspace}:${alias}`;
24995
+ const session = deps.resolveSession(alias, task.agent, task.workspace, transportSession);
24996
+ const noticeText = `执行定时任务 #${task.id}
24997
+ 会话:临时会话(${task.workspace} · ${task.agent})
24998
+ 内容:${preview(task.message)}`;
24999
+ try {
25000
+ await deps.sendScheduledMessage({
25001
+ chatKey: task.chat_key,
25002
+ taskId: task.id,
25003
+ sessionAlias: task.session_alias,
25004
+ sessionDescriptor: { alias, agent: task.agent, workspace: task.workspace, transportSession },
25005
+ noticeText,
25006
+ promptText: task.message,
25007
+ abortSignal,
25008
+ ...task.account_id ? { accountId: task.account_id } : {},
25009
+ ...task.reply_context_token ? { replyContextToken: task.reply_context_token } : {}
24686
25010
  });
25011
+ } finally {
25012
+ if (deps.removeSession) {
25013
+ try {
25014
+ await deps.removeSession(session);
25015
+ } catch (error2) {
25016
+ await deps.logger?.error("scheduled.temp_session_close_failed", "failed to close temp scheduled session", { taskId: task.id, transportSession, error: String(error2) });
25017
+ }
25018
+ }
24687
25019
  }
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));
25020
+ }
25021
+ var init_scheduled_dispatch = __esm(() => {
25022
+ init_channel_scope();
25023
+ init_scheduled_render();
25024
+ });
25025
+
25026
+ // src/scheduled/scheduled-route-create.ts
25027
+ async function createScheduledTaskFromRoute(input, deps) {
25028
+ const coordinatorSession = input.coordinatorSession.trim();
25029
+ if (coordinatorSession.length === 0) {
25030
+ throw new Error("coordinatorSession must be a non-empty string");
24690
25031
  }
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
- });
25032
+ const route = deps.state.orchestration.coordinatorRoutes[coordinatorSession];
25033
+ if (!route) {
25034
+ throw new Error(`no chat route is recorded for coordinator session "${coordinatorSession}"`);
24702
25035
  }
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
- });
25036
+ if (route.chatType !== "direct" && route.chatType !== "group") {
25037
+ throw new Error("scheduled_create requires current chat route metadata");
24723
25038
  }
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
- });
25039
+ if (route.chatType === "group" && route.isOwner !== true) {
25040
+ throw new Error("scheduled_create is owner-only in group chats");
24739
25041
  }
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
- });
25042
+ if (deps.supportsScheduledMessages && !deps.supportsScheduledMessages(route.chatKey)) {
25043
+ throw new Error("current channel does not support scheduled tasks");
24751
25044
  }
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
- });
25045
+ const message = input.message.trim();
25046
+ if (message.length === 0) {
25047
+ throw new Error("message must be a non-empty string");
24764
25048
  }
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");
25049
+ if (message.startsWith("/")) {
25050
+ throw new Error("scheduled_create does not support slash-prefixed weacpx commands");
24772
25051
  }
24773
- async mutate(critical) {
24774
- return await this.stateMutex.run(critical);
25052
+ if (!route.sessionAlias) {
25053
+ throw new Error("scheduled_create requires current session route metadata");
24775
25054
  }
24776
- async save() {
24777
- await this.stateStore.save(this.state);
25055
+ const session = await deps.sessions.getSession(route.sessionAlias);
25056
+ if (!session) {
25057
+ throw new Error(`session "${route.sessionAlias}" recorded for coordinator session "${coordinatorSession}" was not found`);
24778
25058
  }
25059
+ if (session.transportSession !== coordinatorSession) {
25060
+ throw new Error(`session "${route.sessionAlias}" is no longer attached to coordinator session "${coordinatorSession}"`);
25061
+ }
25062
+ const executeAt = parseRouteScheduledTime(input.timeText, deps.now?.() ?? new Date);
25063
+ const mode = input.mode ?? (deps.config.later?.defaultMode === "bind" ? "bound" : "temp");
25064
+ return await deps.scheduled.createTask({
25065
+ chatKey: route.chatKey,
25066
+ sessionAlias: session.alias,
25067
+ executeAt,
25068
+ message,
25069
+ sessionMode: mode,
25070
+ ...mode === "temp" ? { agent: session.agent, workspace: session.workspace } : {},
25071
+ ...route.accountId ? { accountId: route.accountId } : {},
25072
+ ...route.replyContextToken ? { replyContextToken: route.replyContextToken } : {},
25073
+ sourceLabel: "mcp:scheduled_create"
25074
+ });
24779
25075
  }
24780
- function normalizeId(input) {
24781
- return input.trim().replace(/^#/, "").toLowerCase();
25076
+ function parseRouteScheduledTime(timeText, now) {
25077
+ const timeTokens = timeText.trim().split(/\s+/).filter((token) => token.length > 0);
25078
+ if (timeTokens.length === 0) {
25079
+ throw new Error(formatLaterTimeParseError("missing_time"));
25080
+ }
25081
+ const parsed = parseLaterTime([...timeTokens, "__scheduled_create_message__"], now);
25082
+ if (!parsed.ok) {
25083
+ throw new Error(formatLaterTimeParseError(parsed.code, parsed.value));
25084
+ }
25085
+ if (parsed.messageStartIndex !== timeTokens.length) {
25086
+ throw new Error("timeText must contain only the time expression; put the scheduled content in message");
25087
+ }
25088
+ return parsed.executeAt;
24782
25089
  }
24783
- var init_scheduled_service = () => {};
25090
+ function formatLaterTimeParseError(code, value) {
25091
+ switch (code) {
25092
+ case "missing_message":
25093
+ return "message must be provided separately from timeText";
25094
+ case "too_soon":
25095
+ return "scheduled task time must be at least 10 seconds in the future";
25096
+ case "out_of_range":
25097
+ return "scheduled task time must be within 7 days";
25098
+ case "past_today_time":
25099
+ return `today at ${value} has already passed; choose a future time or use tomorrow`;
25100
+ case "unrecognized_time":
25101
+ case "missing_time":
25102
+ default:
25103
+ return "unrecognized timeText; supported examples: in 2h, 30分钟后, tomorrow 09:00, 周五 09:00";
25104
+ }
25105
+ }
25106
+ var init_scheduled_route_create = __esm(() => {
25107
+ init_parse_later_time();
25108
+ });
25109
+
25110
+ // src/scheduled/scheduled-route-manage.ts
25111
+ async function listScheduledTasksFromRoute(input, deps) {
25112
+ resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_list");
25113
+ return deps.scheduled.listPending();
25114
+ }
25115
+ async function cancelScheduledTaskFromRoute(input, deps) {
25116
+ resolveOwnedCoordinatorRoute(input.coordinatorSession, deps.state, "scheduled_cancel");
25117
+ const cancelled = await deps.scheduled.cancelPending(input.id);
25118
+ return { id: normalizeId(input.id), cancelled };
25119
+ }
25120
+ function resolveOwnedCoordinatorRoute(coordinatorSession, state, label) {
25121
+ const session = coordinatorSession.trim();
25122
+ if (session.length === 0) {
25123
+ throw new Error("coordinatorSession must be a non-empty string");
25124
+ }
25125
+ const route = state.orchestration.coordinatorRoutes[session];
25126
+ if (!route) {
25127
+ throw new Error(`no chat route is recorded for coordinator session "${session}"`);
25128
+ }
25129
+ if (route.chatType !== "direct" && route.chatType !== "group") {
25130
+ throw new Error(`${label} requires current chat route metadata`);
25131
+ }
25132
+ if (route.chatType === "group" && route.isOwner !== true) {
25133
+ throw new Error(`${label} is owner-only in group chats`);
25134
+ }
25135
+ return route;
25136
+ }
25137
+ var init_scheduled_route_manage = __esm(() => {
25138
+ init_scheduled_service();
25139
+ });
24784
25140
 
24785
25141
  // src/sessions/session-service.ts
24786
25142
  class SessionService {
@@ -26409,7 +26765,7 @@ import { join as join13 } from "node:path";
26409
26765
  function buildWeacpxMcpServerSpec(input) {
26410
26766
  const { command, args } = splitCommandLine(input.weacpxCommand);
26411
26767
  return {
26412
- name: "weacpx-orchestration",
26768
+ name: "weacpx",
26413
26769
  type: "stdio",
26414
26770
  command,
26415
26771
  args: [
@@ -26417,7 +26773,7 @@ function buildWeacpxMcpServerSpec(input) {
26417
26773
  "mcp-stdio",
26418
26774
  "--coordinator-session",
26419
26775
  input.coordinatorSession,
26420
- ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : []
26776
+ ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
26421
26777
  ]
26422
26778
  };
26423
26779
  }
@@ -27867,32 +28223,32 @@ async function buildApp(paths, deps = {}) {
27867
28223
  }
27868
28224
  const progressHeartbeatInterval = startProgressHeartbeat(orchestration, config2, logger2, deps.channel ?? null);
27869
28225
  const orchestrationEndpoint = createOrchestrationEndpoint(paths.orchestrationSocketPath ?? resolveOrchestrationSocketPathFromConfigPath(paths.configPath));
27870
- const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration);
28226
+ const orchestrationServer = new OrchestrationServer(orchestrationEndpoint, orchestration, {
28227
+ createScheduledTaskFromRoute: async (input) => await createScheduledTaskFromRoute(input, {
28228
+ state,
28229
+ config: config2,
28230
+ sessions,
28231
+ scheduled: scheduledService,
28232
+ ...deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : {}
28233
+ }),
28234
+ listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
28235
+ cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
28236
+ });
27871
28237
  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
28238
  const agent = new ConsoleAgent(router, logger2);
27873
28239
  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
- },
28240
+ dispatchTask: buildScheduledDispatchTask({
28241
+ getSession: (alias) => sessions.getSession(alias),
28242
+ resolveSession: (alias, agent2, workspace, transportSession) => sessions.resolveSession(alias, agent2, workspace, transportSession),
28243
+ sendScheduledMessage: async (input) => {
28244
+ if (!deps.channel?.sendScheduledMessage) {
28245
+ throw new Error("no channel runtime available for scheduled task dispatch");
28246
+ }
28247
+ await deps.channel.sendScheduledMessage(input);
28248
+ },
28249
+ ...transport.removeSession ? { removeSession: (session) => transport.removeSession(session) } : {},
28250
+ logger: logger2
28251
+ }),
27896
28252
  logger: logger2
27897
28253
  });
27898
28254
  return {
@@ -28065,8 +28421,9 @@ var init_main = __esm(async () => {
28065
28421
  init_build_coordinator_prompt();
28066
28422
  init_scheduled_scheduler();
28067
28423
  init_scheduled_service();
28068
- init_scheduled_render();
28069
- init_channel_scope();
28424
+ init_scheduled_dispatch();
28425
+ init_scheduled_route_create();
28426
+ init_scheduled_route_manage();
28070
28427
  init_session_service();
28071
28428
  init_state_store();
28072
28429
  init_run_console();
@@ -41598,12 +41955,13 @@ var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
41598
41955
  var orderSchema = exports_external.enum(["asc", "desc"]);
41599
41956
  var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
41600
41957
  var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
41958
+ var scheduledModeSchema = exports_external.enum(["temp", "bound"]);
41601
41959
  var taskQuestionSchema = exports_external.object({
41602
41960
  taskId: exports_external.string().min(1),
41603
41961
  questionId: exports_external.string().min(1)
41604
41962
  }).strict();
41605
41963
  function buildWeacpxMcpToolRegistry(input) {
41606
- const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, availableAgents } = input;
41964
+ const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, internalSessionTools, availableAgents } = input;
41607
41965
  const tools = [
41608
41966
  {
41609
41967
  name: "delegate_request",
@@ -41821,6 +42179,63 @@ function buildWeacpxMcpToolRegistry(input) {
41821
42179
  })
41822
42180
  }
41823
42181
  ];
42182
+ if (internalSessionTools && !isExternalCoordinator && !sourceHandle) {
42183
+ tools.push({
42184
+ name: "scheduled_create",
42185
+ description: "Schedule a one-shot task to run a natural-language message at a future time, using the recorded chat route. By default — and like /later — the task runs in a FRESH TEMPORARY session (it snapshots the current agent and workspace but starts with brand-new history and is destroyed after running, so it does not pollute this conversation); the reply is still pushed back to this chat. Provide only timeText and message and OMIT mode to get this default. Routing, session, and account are resolved by weacpx.",
42186
+ inputSchema: exports_external.object({
42187
+ timeText: exports_external.string().min(1).describe("Time expression, e.g. 'in 2h', '30分钟后', 'tomorrow 09:00', or '周五 09:00'."),
42188
+ message: exports_external.string().min(1).describe("Natural-language message to run at the scheduled time."),
42189
+ mode: scheduledModeSchema.describe("Optional; leave UNSET for the default temporary session (recommended). Set 'bound' ONLY when the user explicitly asks for the task to run inside this conversation's current session and share its context. 'temp' forces the temporary session.").optional()
42190
+ }).strict(),
42191
+ handler: async (args) => await asToolResult(async () => {
42192
+ const input2 = args;
42193
+ const task = await transport.scheduledCreate({
42194
+ coordinatorSession,
42195
+ timeText: input2.timeText,
42196
+ message: input2.message,
42197
+ ...input2.mode ? { mode: input2.mode } : {}
42198
+ });
42199
+ return createSuccessResult(`Scheduled task #${task.id} created for ${task.execute_at}.`, {
42200
+ id: task.id,
42201
+ status: task.status,
42202
+ executeAt: task.execute_at,
42203
+ sessionAlias: task.session_alias,
42204
+ sessionMode: task.session_mode ?? "bound"
42205
+ });
42206
+ })
42207
+ });
42208
+ tools.push({
42209
+ name: "scheduled_list",
42210
+ 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.",
42211
+ inputSchema: exports_external.object({}).strict(),
42212
+ handler: async () => await asToolResult(async () => {
42213
+ const tasks = await transport.scheduledList({ coordinatorSession });
42214
+ return createSuccessResult(renderScheduledList(tasks), {
42215
+ tasks: tasks.map((task) => ({
42216
+ id: task.id,
42217
+ executeAt: task.execute_at,
42218
+ message: task.message,
42219
+ sessionAlias: task.session_alias,
42220
+ sessionMode: task.session_mode ?? "bound",
42221
+ chatKey: task.chat_key
42222
+ }))
42223
+ });
42224
+ })
42225
+ });
42226
+ tools.push({
42227
+ name: "scheduled_cancel",
42228
+ 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.",
42229
+ inputSchema: exports_external.object({
42230
+ id: exports_external.string().min(1).describe("The scheduled task id, e.g. 'k8f2' (a leading # is allowed).")
42231
+ }).strict(),
42232
+ handler: async (args) => await asToolResult(async () => {
42233
+ const { id } = args;
42234
+ const result = await transport.scheduledCancel({ coordinatorSession, id });
42235
+ return createSuccessResult(renderScheduledCancel(result), { id: result.id, cancelled: result.cancelled });
42236
+ })
42237
+ });
42238
+ }
41824
42239
  if (isExternalCoordinator) {
41825
42240
  const externalCoordinatorIncompatibleTools = new Set([
41826
42241
  "coordinator_request_human_input"
@@ -41900,6 +42315,19 @@ function renderTaskList(tasks) {
41900
42315
  return ["Tasks for the current coordinator:", ...tasks.map((task) => renderTaskListItem(task))].join(`
41901
42316
  `);
41902
42317
  }
42318
+ function renderScheduledList(tasks) {
42319
+ if (tasks.length === 0) {
42320
+ return "There are no pending scheduled tasks.";
42321
+ }
42322
+ return [
42323
+ "Pending scheduled tasks:",
42324
+ ...tasks.map((task) => `- #${task.id} at ${task.execute_at} [${task.session_mode ?? "bound"}] -> ${task.session_alias}: ${task.message}`)
42325
+ ].join(`
42326
+ `);
42327
+ }
42328
+ function renderScheduledCancel(result) {
42329
+ return result.cancelled ? `Scheduled task #${result.id} cancelled.` : `No pending scheduled task #${result.id} found.`;
42330
+ }
41903
42331
  function renderTaskListItem(task) {
41904
42332
  const role = task.role ? ` / ${task.role}` : "";
41905
42333
  const group = task.groupId ? `; group: ${task.groupId}` : "";
@@ -42070,6 +42498,15 @@ class OrchestrationClient {
42070
42498
  async createGroup(input) {
42071
42499
  return await this.request("group.new", input);
42072
42500
  }
42501
+ async scheduledCreate(input) {
42502
+ return await this.request("scheduled.create", input);
42503
+ }
42504
+ async scheduledList(input) {
42505
+ return await this.request("scheduled.list", input);
42506
+ }
42507
+ async scheduledCancel(input) {
42508
+ return await this.request("scheduled.cancel", input);
42509
+ }
42073
42510
  async request(method, params, timeoutMs = this.timeoutMs) {
42074
42511
  const id = this.createId();
42075
42512
  return await new Promise((resolve, reject) => {
@@ -42182,7 +42619,25 @@ function createOrchestrationTransport(endpoint, deps = {}) {
42182
42619
  },
42183
42620
  coordinatorAnswerQuestion: async (input) => await client.coordinatorAnswerQuestion(input),
42184
42621
  coordinatorRequestHumanInput: async (input) => await client.coordinatorRequestHumanInput(input),
42185
- coordinatorReviewContestedResult: async (input) => await client.coordinatorReviewContestedResult(input)
42622
+ coordinatorReviewContestedResult: async (input) => await client.coordinatorReviewContestedResult(input),
42623
+ scheduledCreate: async (input) => {
42624
+ if (!client.scheduledCreate) {
42625
+ throw new Error("orchestration client scheduledCreate is not configured");
42626
+ }
42627
+ return await client.scheduledCreate(input);
42628
+ },
42629
+ scheduledList: async (input) => {
42630
+ if (!client.scheduledList) {
42631
+ throw new Error("orchestration client scheduledList is not configured");
42632
+ }
42633
+ return await client.scheduledList(input);
42634
+ },
42635
+ scheduledCancel: async (input) => {
42636
+ if (!client.scheduledCancel) {
42637
+ throw new Error("orchestration client scheduledCancel is not configured");
42638
+ }
42639
+ return await client.scheduledCancel(input);
42640
+ }
42186
42641
  };
42187
42642
  }
42188
42643
 
@@ -42206,7 +42661,7 @@ function createWeacpxMcpServer(options) {
42206
42661
  const taskOptionsById = new Map;
42207
42662
  const watchTasksById = new Map;
42208
42663
  const server = new Server({
42209
- name: "weacpx-orchestration",
42664
+ name: "weacpx",
42210
42665
  version: readVersion()
42211
42666
  }, {
42212
42667
  capabilities: {
@@ -42238,6 +42693,7 @@ function createWeacpxMcpServer(options) {
42238
42693
  coordinatorSession: identity.coordinatorSession,
42239
42694
  ...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {},
42240
42695
  ...identity.isExternalCoordinator ? { isExternalCoordinator: true } : {},
42696
+ ...identity.internalSessionTools ? { internalSessionTools: true } : {},
42241
42697
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
42242
42698
  });
42243
42699
  return toolState;
@@ -42673,7 +43129,8 @@ async function resolveMcpIdentity(server, options) {
42673
43129
  return {
42674
43130
  coordinatorSession: options.coordinatorSession,
42675
43131
  ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
42676
- ...options.isExternalCoordinator ? { isExternalCoordinator: true } : {}
43132
+ ...options.isExternalCoordinator ? { isExternalCoordinator: true } : {},
43133
+ ...options.internalSessionTools ? { internalSessionTools: true } : {}
42677
43134
  };
42678
43135
  }
42679
43136
  throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
@@ -42762,6 +43219,7 @@ async function runWeacpxMcpServer(options) {
42762
43219
  transport,
42763
43220
  ...options.coordinatorSession ? { coordinatorSession: options.coordinatorSession } : {},
42764
43221
  ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
43222
+ ...options.internalSessionTools ? { internalSessionTools: true } : {},
42765
43223
  ...options.resolveIdentity ? { resolveIdentity: options.resolveIdentity } : {},
42766
43224
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
42767
43225
  });
@@ -42856,6 +43314,11 @@ function parseCoordinatorSession(args, env = process.env) {
42856
43314
  });
42857
43315
  }
42858
43316
 
43317
+ // src/mcp/parse-internal-session-tools.ts
43318
+ function parseInternalSessionToolsFlag(args, _env = process.env) {
43319
+ return args.includes("--internal-session-tools");
43320
+ }
43321
+
42859
43322
  // src/mcp/parse-source-handle.ts
42860
43323
  function parseSourceHandle(args, env = process.env) {
42861
43324
  return parseStringFlag(args, env, {
@@ -42868,13 +43331,19 @@ function parseSourceHandle(args, env = process.env) {
42868
43331
  init_workspace_path();
42869
43332
  init_workspace_name();
42870
43333
  init_state_store();
43334
+ init_channel_scope();
43335
+ init_scheduled_render();
43336
+ init_scheduled_service();
42871
43337
 
42872
43338
  // src/onboarding.ts
42873
43339
  init_workspace_path();
42874
43340
  init_workspace_name();
43341
+ init_default_workspace();
42875
43342
  init_agent_templates();
42876
43343
  function isFirstUse(config2, state) {
42877
- return Object.keys(state.sessions ?? {}).length === 0 && Object.keys(config2.workspaces ?? {}).length === 0 && (config2.plugins ?? []).length === 0;
43344
+ const workspaceNames = Object.keys(config2.workspaces ?? {});
43345
+ const onlyDefaultOrEmpty = workspaceNames.length === 0 || workspaceNames.length === 1 && workspaceNames[0] === DEFAULT_HOME_WORKSPACE_NAME;
43346
+ return Object.keys(state.sessions ?? {}).length === 0 && onlyDefaultOrEmpty && (config2.plugins ?? []).length === 0;
42878
43347
  }
42879
43348
  async function maybeRunFirstUseOnboarding(input) {
42880
43349
  if (!isFirstUse(input.config, input.state))
@@ -44542,7 +45011,8 @@ function createMcpStdioIdentityResolver(input) {
44542
45011
  return {
44543
45012
  coordinatorSession: resolvedCoordinatorSession,
44544
45013
  ...sourceHandle ? { sourceHandle } : {},
44545
- ...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {}
45014
+ ...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {},
45015
+ ...input.internalSessionTools && startup.kind === "existing-session" && !sourceHandle ? { internalSessionTools: true } : {}
44546
45016
  };
44547
45017
  };
44548
45018
  }
@@ -44587,6 +45057,7 @@ var HELP_LINES = [
44587
45057
  "weacpx version - 查看版本",
44588
45058
  "weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
44589
45059
  "weacpx workspace list|add [name] [--raw]|rm <name> - 管理本机工作区(别名:ws)",
45060
+ "weacpx later|lt list|cancel <id> - 管理本机待执行定时任务",
44590
45061
  "weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
44591
45062
  ];
44592
45063
  function getUsageText() {
@@ -44674,6 +45145,17 @@ async function runCli(args, deps = {}) {
44674
45145
  }
44675
45146
  return result;
44676
45147
  }
45148
+ case "later":
45149
+ case "lt": {
45150
+ const result = await handleLaterCli(args.slice(1), { print });
45151
+ if (result === null) {
45152
+ for (const line of HELP_LINES) {
45153
+ print(line);
45154
+ }
45155
+ return 1;
45156
+ }
45157
+ return result;
45158
+ }
44677
45159
  case "plugin": {
44678
45160
  const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
44679
45161
  print,
@@ -45022,6 +45504,48 @@ async function agentRemove(rawName, print) {
45022
45504
  print(`Agent「${name}」已删除`);
45023
45505
  return 0;
45024
45506
  }
45507
+ async function handleLaterCli(args, deps) {
45508
+ const subcommand = args[0];
45509
+ switch (subcommand) {
45510
+ case "list":
45511
+ if (args.length !== 1)
45512
+ return null;
45513
+ return await laterList(deps.print);
45514
+ case "cancel":
45515
+ if (args.length !== 2 || !args[1])
45516
+ return null;
45517
+ return await laterCancel(args[1], deps.print);
45518
+ default:
45519
+ return null;
45520
+ }
45521
+ }
45522
+ async function laterList(print) {
45523
+ const scheduled = await createCliScheduledTaskService();
45524
+ print(renderLaterList(scheduled.listPending(), (alias) => toDisplaySessionAlias(alias)));
45525
+ return 0;
45526
+ }
45527
+ async function laterCancel(rawId, print) {
45528
+ const id = normalizeId(rawId);
45529
+ if (id.length === 0) {
45530
+ print("定时任务 ID 不能为空。");
45531
+ return 1;
45532
+ }
45533
+ const scheduled = await createCliScheduledTaskService();
45534
+ const ok = await scheduled.cancelPending(id);
45535
+ if (!ok) {
45536
+ print(`未找到待执行的定时任务 #${id}。`);
45537
+ print("可以用 weacpx later list 查看当前待执行任务。");
45538
+ return 1;
45539
+ }
45540
+ print(`已取消定时任务 #${id}`);
45541
+ return 0;
45542
+ }
45543
+ async function createCliScheduledTaskService() {
45544
+ const runtimePaths = (await init_main().then(() => exports_main)).resolveRuntimePaths();
45545
+ const stateStore = new StateStore(runtimePaths.statePath);
45546
+ const state = await stateStore.load();
45547
+ return new ScheduledTaskService(state, stateStore);
45548
+ }
45025
45549
  function resolveConfigPathForCurrentEnv() {
45026
45550
  return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
45027
45551
  }
@@ -45148,10 +45672,12 @@ async function defaultMcpStdio(args, deps = {}) {
45148
45672
  let transport;
45149
45673
  let identityResolver;
45150
45674
  let availableAgents;
45675
+ let internalSessionTools = false;
45151
45676
  try {
45152
45677
  const parsedCoordinatorSession = parseCoordinatorSession(args, process.env);
45153
45678
  sourceHandle = parseSourceHandle(args, process.env);
45154
45679
  const workspace = parseCoordinatorWorkspace(args, process.env);
45680
+ const requestedInternalSessionTools = parseInternalSessionToolsFlag(args, process.env);
45155
45681
  endpoint = resolveDefaultOrchestrationEndpoint(process.env, process.platform);
45156
45682
  const client = new OrchestrationClient(endpoint);
45157
45683
  transport = createOrchestrationTransport(endpoint, { client });
@@ -45166,10 +45692,12 @@ async function defaultMcpStdio(args, deps = {}) {
45166
45692
  workspace,
45167
45693
  config: config2,
45168
45694
  state,
45169
- client
45695
+ client,
45696
+ internalSessionTools: requestedInternalSessionTools
45170
45697
  });
45171
45698
  const eagerIdentity = parsedCoordinatorSession ? await resolveIdentity({ clientName: undefined, listRoots: async () => [] }) : null;
45172
45699
  coordinatorSession = eagerIdentity?.coordinatorSession ?? "";
45700
+ internalSessionTools = eagerIdentity?.internalSessionTools ?? false;
45173
45701
  identityResolver = eagerIdentity ? undefined : resolveIdentity;
45174
45702
  } catch (error2) {
45175
45703
  (deps.stderr ?? ((text) => process.stderr.write(text)))(`${error2 instanceof Error ? error2.message : String(error2)}
@@ -45180,6 +45708,7 @@ async function defaultMcpStdio(args, deps = {}) {
45180
45708
  transport,
45181
45709
  ...coordinatorSession ? { coordinatorSession } : {},
45182
45710
  ...sourceHandle ? { sourceHandle } : {},
45711
+ ...internalSessionTools ? { internalSessionTools: true } : {},
45183
45712
  ...identityResolver ? { resolveIdentity: identityResolver } : {},
45184
45713
  ...availableAgents ? { availableAgents } : {},
45185
45714
  onDiagnostic: (event, context) => {