tellrun 1.3.8 → 1.3.9

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.
@@ -0,0 +1,226 @@
1
+ import { Cron } from "croner";
2
+ import { activeTaskStatuses } from "../storage/schema.js";
3
+ /** 只使用五段 Cron;Croner 负责校验和时区计算,不创建独立执行定时器。 */
4
+ function nextRun(expression, timezone, after) {
5
+ let cron;
6
+ try {
7
+ if (expression.trim().split(/\s+/).length !== 5)
8
+ return null;
9
+ cron = new Cron(expression, { timezone, mode: "5-part", paused: true });
10
+ return cron.nextRun(new Date(after))?.getTime() ?? null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ finally {
16
+ cron?.stop();
17
+ }
18
+ }
19
+ /** 时间按计划保存的时区展示,避免服务器时区变更后误读旧任务。 */
20
+ function formatTime(timestamp, timezone) {
21
+ if (timestamp === null)
22
+ return "—";
23
+ return new Intl.DateTimeFormat("en-CA", {
24
+ timeZone: timezone,
25
+ year: "numeric",
26
+ month: "2-digit",
27
+ day: "2-digit",
28
+ hour: "2-digit",
29
+ minute: "2-digit",
30
+ hourCycle: "h23",
31
+ }).format(timestamp);
32
+ }
33
+ /** 命令、触发记录和任务创建共用存储事务;计划本身不直接调用 Agent。 */
34
+ export function createCrons(storage) {
35
+ /** 调度只读取当前机器人和授权用户的计划,旧配置的计划不能误触发。 */
36
+ function channelsFor(source) {
37
+ return new Map(storage
38
+ .read()
39
+ .channels.filter((channel) => channel.platform === source.platform &&
40
+ channel.accountId === source.accountId &&
41
+ channel.userId === source.userId)
42
+ .map((channel) => [channel.id, channel]));
43
+ }
44
+ /** 到期时向未来推进;无效计划关闭并通知,避免反复触发或阻塞其他计划。 */
45
+ function advance(cron, now) {
46
+ cron.nextRunAt = nextRun(cron.expression, cron.timezone, now);
47
+ if (cron.nextRunAt !== null)
48
+ return;
49
+ cron.enabled = false;
50
+ cron.pendingAt = null;
51
+ storage.enqueueReply(cron.channelId, `Cron #${cron.id} disabled: invalid expression or timezone, or no future run. Delete and recreate the schedule.`);
52
+ }
53
+ return {
54
+ /** 已接收命令由外层事务去重;参数校验完成后才修改计划。 */
55
+ handleCron(channelId, args, now = Date.now()) {
56
+ const state = storage.read();
57
+ const channel = state.channels.find((item) => item.id === channelId);
58
+ if (!channel?.cwd)
59
+ return "Set a working directory with /cwd first.";
60
+ const cwd = channel.cwd;
61
+ /** 新计划读取创建时的服务器时区,已有计划始终使用保存的时区。 */
62
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
63
+ /** 帮助与参数错误共用命令格式,描述可以包含空格和换行。 */
64
+ const usage = [
65
+ "/cron — List all scheduled tasks",
66
+ "/cron add <minute> <hour> <day> <month> <weekday> <task>",
67
+ "/cron del <id> — Delete a schedule",
68
+ "/cron enable <id> — Enable a schedule",
69
+ "/cron disable <id> — Disable a schedule",
70
+ `New schedule timezone: ${timezone} (server timezone)`,
71
+ "Example: /cron add 0 9 * * 1-5 Check the project and report issues",
72
+ ].join("\n");
73
+ const input = args.trim();
74
+ const paused = channel.paused
75
+ ? "\nChat paused. Send /resume to allow future scheduled runs."
76
+ : "";
77
+ if (!input) {
78
+ const crons = state.crons.filter((cron) => cron.channelId === channelId);
79
+ if (!crons.length)
80
+ return `No scheduled tasks.${paused}\n\n${usage}`;
81
+ return [
82
+ `Scheduled tasks: ${crons.length}${paused}`,
83
+ ...crons.map((cron) => {
84
+ const running = state.tasks.find((task) => task.cron?.id === cron.id && activeTaskStatuses.has(task.status));
85
+ const activity = [
86
+ running ? `Active task: #${running.id}` : "",
87
+ cron.pendingAt !== null ? "Pending" : "",
88
+ ]
89
+ .filter(Boolean)
90
+ .join(" · ");
91
+ return `#${cron.id} · ${cron.enabled ? "Enabled" : "Disabled"}${activity ? ` · ${activity}` : ""}\n${cron.expression}\nTimezone: ${cron.timezone}\nDirectory: ${cron.cwd}\nTask: ${cron.prompt}\nNext: ${formatTime(cron.nextRunAt, cron.timezone)}`;
92
+ }),
93
+ ].join("\n\n");
94
+ }
95
+ const add = input.match(/^add\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([\s\S]+)$/);
96
+ if (add) {
97
+ const expression = add.slice(1, 6).join(" ");
98
+ const prompt = add[6]?.trim();
99
+ const nextRunAt = nextRun(expression, timezone, now);
100
+ if (!prompt || nextRunAt === null)
101
+ return `Invalid cron expression or no future run. Use five fields.\n\n${usage}`;
102
+ return storage.update(() => {
103
+ const state = storage.read();
104
+ const id = state.nextCronId++;
105
+ state.crons.push({
106
+ id,
107
+ channelId,
108
+ expression,
109
+ timezone,
110
+ prompt,
111
+ cwd,
112
+ model: channel.model,
113
+ reasoningEffort: channel.reasoningEffort,
114
+ permissionMode: channel.permissionMode,
115
+ enabled: true,
116
+ nextRunAt,
117
+ pendingAt: null,
118
+ });
119
+ return `Cron #${id} created (enabled).\n${expression}\nTimezone: ${timezone}\nDirectory: ${cwd}\nTask: ${prompt}\nNext: ${formatTime(nextRunAt, timezone)}${paused}`;
120
+ });
121
+ }
122
+ const action = input.match(/^(del|enable|disable)\s+([1-9]\d*)$/);
123
+ if (!action || !Number.isSafeInteger(Number(action[2])))
124
+ return usage;
125
+ const cron = state.crons.find((item) => item.id === Number(action[2]) && item.channelId === channelId);
126
+ if (!cron)
127
+ return "Schedule not found. Send /cron to see task IDs.";
128
+ if (action[1] === "enable" && cron.enabled)
129
+ return `Cron #${cron.id} is already enabled.\nNext: ${formatTime(cron.nextRunAt, cron.timezone)} (${cron.timezone})${paused}`;
130
+ const nextRunAt = action[1] === "enable" ? nextRun(cron.expression, cron.timezone, now) : null;
131
+ if (action[1] === "enable" && nextRunAt === null)
132
+ return "No future run could be calculated. Delete and recreate the schedule.";
133
+ return storage.update(() => {
134
+ const state = storage.read();
135
+ const current = state.crons.find((item) => item.id === cron.id);
136
+ if (!current)
137
+ return "Schedule not found.";
138
+ const active = state.tasks.some((task) => task.cron?.id === current.id && activeTaskStatuses.has(task.status));
139
+ if (action[1] === "del")
140
+ state.crons = state.crons.filter((item) => item.id !== current.id);
141
+ else {
142
+ current.enabled = action[1] === "enable";
143
+ current.nextRunAt = nextRunAt;
144
+ current.pendingAt = null;
145
+ }
146
+ return `Cron #${current.id} ${action[1] === "del" ? "deleted" : current.enabled ? "enabled" : "disabled"}.${action[1] === "enable" ? `\nNext: ${formatTime(nextRunAt, current.timezone)} (${current.timezone})${paused}` : active ? "\nThe active task will continue. Use /stop to stop it." : ""}`;
147
+ });
148
+ },
149
+ /** 暂停清空等待触发;恢复时跳过暂停期间到期的次数,不产生过期工作。 */
150
+ resetCronPending(channelId, now = Date.now()) {
151
+ storage.update(() => {
152
+ for (const cron of storage.read().crons) {
153
+ if (cron.channelId !== channelId)
154
+ continue;
155
+ cron.pendingAt = null;
156
+ if (cron.enabled && (cron.nextRunAt === null || cron.nextRunAt <= now))
157
+ advance(cron, now);
158
+ }
159
+ });
160
+ },
161
+ /** 只跳过停机期间错过的触发;停机前已记录的待执行项继续保留。 */
162
+ recoverCrons(source, now = Date.now()) {
163
+ const channels = channelsFor(source);
164
+ const due = storage
165
+ .read()
166
+ .crons.filter((cron) => channels.has(cron.channelId) &&
167
+ ((cron.enabled && (cron.nextRunAt === null || cron.nextRunAt <= now)) ||
168
+ (channels.get(cron.channelId)?.paused && cron.pendingAt !== null)));
169
+ if (!due.length)
170
+ return;
171
+ storage.update(() => {
172
+ for (const saved of due) {
173
+ const cron = storage.read().crons.find((item) => item.id === saved.id);
174
+ if (!cron)
175
+ continue;
176
+ if (channels.get(cron.channelId)?.paused)
177
+ cron.pendingAt = null;
178
+ if (cron.enabled && (cron.nextRunAt === null || cron.nextRunAt <= now))
179
+ advance(cron, now);
180
+ }
181
+ });
182
+ },
183
+ /** 触发、下一时刻和执行快照一次落盘;忙时每个计划只保留一次,空闲按时间串行领取。 */
184
+ tickCrons(source, now = Date.now()) {
185
+ const channels = channelsFor(source);
186
+ const crons = storage
187
+ .read()
188
+ .crons.filter((cron) => cron.enabled && channels.has(cron.channelId));
189
+ const busy = storage.read().tasks.some((task) => activeTaskStatuses.has(task.status));
190
+ if (!crons.some((cron) => (cron.nextRunAt !== null && cron.nextRunAt <= now) ||
191
+ (cron.pendingAt !== null && (!busy || channels.get(cron.channelId)?.paused))))
192
+ return;
193
+ storage.update(() => {
194
+ const state = storage.read();
195
+ for (const cron of state.crons) {
196
+ const channel = channels.get(cron.channelId);
197
+ if (!cron.enabled || !channel)
198
+ continue;
199
+ if (channel.paused)
200
+ cron.pendingAt = null;
201
+ if (cron.nextRunAt === null || cron.nextRunAt > now)
202
+ continue;
203
+ if (!channel.paused)
204
+ cron.pendingAt ??= cron.nextRunAt;
205
+ advance(cron, now);
206
+ }
207
+ if (state.tasks.some((task) => activeTaskStatuses.has(task.status)))
208
+ return;
209
+ const cron = state.crons
210
+ .filter((item) => item.enabled &&
211
+ item.pendingAt !== null &&
212
+ channels.has(item.channelId) &&
213
+ !channels.get(item.channelId)?.paused)
214
+ .sort((a, b) => (a.pendingAt ?? 0) - (b.pendingAt ?? 0) || a.id - b.id)[0];
215
+ const channel = cron && state.channels.find((item) => item.id === cron.channelId);
216
+ if (!cron || !channel || cron.pendingAt === null)
217
+ return;
218
+ const scheduledAt = cron.pendingAt;
219
+ cron.pendingAt = null;
220
+ const task = storage.createTask(channel, cron, scheduledAt);
221
+ storage.enqueueReply(channel.id, `Cron #${cron.id} queued task #${task.id}.\nScheduled: ${formatTime(scheduledAt, cron.timezone)} (${cron.timezone})\nDirectory: ${cron.cwd}\nTask: ${cron.prompt}`);
222
+ });
223
+ },
224
+ };
225
+ }
226
+ //# sourceMappingURL=cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/core/cron.ts"],"names":[],"mappings":"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"}
@@ -46,10 +46,9 @@ export function createMenus(storage) {
46
46
  .channels.find((channel) => channel.platform === source.platform &&
47
47
  channel.accountId === source.accountId &&
48
48
  channel.userId === source.userId &&
49
- channel.cwd &&
50
49
  channel.modelMenu?.stage === "loading" &&
51
50
  channel.modelMenu.expiresAt > Date.now());
52
- if (channel?.modelMenu && channel.cwd)
51
+ if (channel?.modelMenu)
53
52
  return { channelId: channel.id, cwd: channel.cwd, id: channel.modelMenu.id };
54
53
  },
55
54
  /** 新菜单、换目录和过期后丢弃旧查询;失败时保留原模型选择。 */
@@ -1 +1 @@
1
- {"version":3,"file":"menus.js","sourceRoot":"","sources":["../../src/core/menus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"menus.js","sourceRoot":"","sources":["../../src/core/menus.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,22 @@
1
1
  import { realpathSync, statSync } from "node:fs";
2
2
  import { isAbsolute } from "node:path";
3
3
  import { setTimeout } from "node:timers/promises";
4
+ import { AttachmentError, maxAttachmentBytes } from "../storage/attachments.js";
4
5
  /** 接收消息只保存必要状态;任务执行独立串行运行,控制命令始终可用。 */
5
6
  export function createRelayService(options) {
6
7
  let stopping = false;
8
+ /** 当前任务的附件准备可独立取消,不能阻塞停止指令或拖住服务退出。 */
9
+ let inputPreparation;
7
10
  /** 菜单查询独立于任务,关停时取消查询并等待退出后再关闭存储。 */
8
11
  const modelLoading = new AbortController();
9
- /** 状态的模型与版本查询复用独立工作循环,不阻塞入站审批和停止指令。 */
10
- const statusQueries = [];
12
+ /** 状态与会话命令复用独立工作循环,不阻塞入站审批和停止指令。 */
13
+ const controlQueries = [];
14
+ /** 会话变更确认前暂缓新任务,避免接口等待期间把消息发往旧会话。 */
15
+ let changingSession = false;
11
16
  /** 状态查询与历史结果共用面向用户的状态名称。 */
12
17
  const statuses = {
13
18
  queued: "Queued",
19
+ preparing: "Downloading attachments",
14
20
  dispatching: "Connecting",
15
21
  running: "Running",
16
22
  cancelling: "Stopping",
@@ -25,11 +31,8 @@ export function createRelayService(options) {
25
31
  async handleMessage(message) {
26
32
  if (stopping)
27
33
  throw new Error("Service stopping. Message not accepted.");
28
- let statusQuery;
34
+ let controlQuery;
29
35
  const reply = options.store.accept(message, (channel) => {
30
- /** 未指定目录时沿用服务启动目录,并随消息一同保存,后续可用 /cwd 切换。 */
31
- if (!channel.cwd)
32
- options.store.setSession(channel.id, realpathSync(process.cwd()));
33
36
  const task = options.store.activeTask();
34
37
  if (message.content.type === "permissions" || message.content.type === "model")
35
38
  return options.store.chooseOption(channel.id, message.content);
@@ -39,11 +42,20 @@ export function createRelayService(options) {
39
42
  if (answer !== undefined || message.content.type === "interaction")
40
43
  return answer;
41
44
  }
45
+ if (changingSession &&
46
+ (message.content.type === "text" ||
47
+ message.content.type === "attachments" ||
48
+ (message.content.type === "command" &&
49
+ (["new", "switch", "list", "update"].includes(message.content.name) ||
50
+ (message.content.name === "cwd" && message.content.args)))))
51
+ return "Session change in progress. Wait for confirmation, then resend your message.";
42
52
  if (message.content.type === "command") {
43
53
  switch (message.content.name) {
44
54
  case "start":
45
55
  case "help":
46
- return "Tellrun ready.\nSend text to chat with Codex. New messages add to the running task.\n/cwd <path> — Set directory; start a new session\n/new — New session, same directory\n/model — Select model and reasoning effort\n/permissions — Select permission mode\n/status — Status and update check\n/update — Ask Codex to upgrade Tellrun\n/last — Last result\n/stop — Stop task and pause\n/resume — Accept new tasks\nUse buttons for approvals. Approvals wait for your response. Answer questions with buttons or replies. Questions expire within 10 minutes.";
56
+ return "Tellrun ready.\nSend text, photos, or files to Codex (up to 20 MB per attachment). Add a caption to describe your task. New messages add to the running task.\n/cwd <path> — Set directory; start a new session\n/new [name] — Create and select a new session\n/list — List sessions in the current project\n/switch <id> — Select a session by its full ID\n/model — Select model and reasoning effort\n/permissions — Select permission mode\n/cron — Manage scheduled tasks\n/status — Status and update check\n/update — Ask Codex to upgrade Tellrun\n/last — Last result\n/stop — Stop task and pause\n/resume — Accept new tasks\nWait for the task to finish before creating or switching sessions.";
57
+ case "cron":
58
+ return options.store.handleCron(channel.id, message.content.args);
47
59
  case "model":
48
60
  if (message.content.args)
49
61
  return "Use /model to choose a model with the buttons.";
@@ -55,11 +67,11 @@ export function createRelayService(options) {
55
67
  case "status": {
56
68
  /** 查询期间保存请求时的选择,后续切换不会改写这次状态快照。 */
57
69
  const snapshot = structuredClone(channel);
58
- statusQuery = async () => {
70
+ controlQuery = async () => {
59
71
  /** 未选择时读取有效配置;读取失败仅回退到明确保存的模型与档位。 */
60
72
  const describe = async (selection) => {
61
73
  try {
62
- return await options.agent.describeModel(selection.cwd ?? process.cwd(), selection, modelLoading.signal);
74
+ return await options.agent.describeModel(selection.cwd, selection, modelLoading.signal);
63
75
  }
64
76
  catch {
65
77
  return selection.model && selection.reasoningEffort
@@ -100,7 +112,7 @@ export function createRelayService(options) {
100
112
  const upgradeHint = update?.available
101
113
  ? `\n\nSend /update to ask ${options.agent.name} to upgrade Tellrun.`
102
114
  : "";
103
- options.store.reply(snapshot.id, `Service: connected\nTellrun: ${options.updates.version}${versionSuffix}\n${options.agent.name}: ${options.agent.version}\nDirectory: ${snapshot.cwd}\nModel: ${formatModel(model)}${showTaskModel ? `\nTask model: ${formatModel(taskModel)}` : ""}\nPermissions: ${options.agent.permissionModes.find((mode) => mode.id === snapshot.permissionMode)?.label ?? snapshot.permissionMode ?? options.agent.permissionModes[0]?.label ?? "Not set"}${task && task.permissionMode !== snapshot.permissionMode ? `\nTask permissions: ${options.agent.permissionModes.find((mode) => mode.id === task.permissionMode)?.label ?? task.permissionMode ?? options.agent.permissionModes[0]?.label ?? "Not set"}` : ""}\nTask: ${task ? `#${task.id} ${statuses[task.status]}` : "idle"}${task?.interactions.length ? `\nPending: ${task.interactions.map((item) => `${item.kind === "approval" ? "Approval" : "Question"}${item.blocking ? " (waiting)" : " (task running)"}`).join(", ")}` : ""}${upgradeHint}`);
115
+ options.store.reply(snapshot.id, `Service: connected\nTellrun: ${options.updates.version}${versionSuffix}\n${options.agent.name}: ${options.agent.version}\nDirectory: ${snapshot.cwd}${task && task.cwd !== snapshot.cwd ? `\nTask directory: ${task.cwd}` : ""}\nSession: ${snapshot.sessionId ?? "Not started"}\nModel: ${formatModel(model)}${showTaskModel ? `\nTask model: ${formatModel(taskModel)}` : ""}\nPermissions: ${options.agent.permissionModes.find((mode) => mode.id === snapshot.permissionMode)?.label ?? snapshot.permissionMode ?? options.agent.permissionModes[0]?.label ?? "Not set"}${task && task.permissionMode !== snapshot.permissionMode ? `\nTask permissions: ${options.agent.permissionModes.find((mode) => mode.id === task.permissionMode)?.label ?? task.permissionMode ?? options.agent.permissionModes[0]?.label ?? "Not set"}` : ""}\nTask: ${task ? `#${task.id} ${statuses[task.status]}${task.cron ? ` · Cron #${task.cron.id}` : ""}` : "idle"}${task?.interactions.length ? `\nPending: ${task.interactions.map((item) => `${item.kind === "approval" ? "Approval" : "Question"}${item.blocking ? " (waiting)" : " (task running)"}`).join(", ")}` : ""}${upgradeHint}`);
104
116
  };
105
117
  return;
106
118
  }
@@ -131,14 +143,71 @@ export function createRelayService(options) {
131
143
  return "Cannot access directory. Check the path and permissions.";
132
144
  }
133
145
  if (cwd !== channel.cwd)
134
- options.store.setSession(channel.id, cwd);
146
+ options.store.setSession(channel.id, cwd, null);
135
147
  return `Directory: ${cwd}\n${channel.paused ? "Paused. Send /resume to continue." : "Send a task to Codex."}`;
136
148
  }
149
+ case "list": {
150
+ if (message.content.args)
151
+ return "Use /list without arguments.";
152
+ const snapshot = structuredClone(channel);
153
+ controlQuery = async () => {
154
+ let sessions;
155
+ try {
156
+ sessions = await options.agent.listSessions(snapshot.cwd, modelLoading.signal);
157
+ }
158
+ catch {
159
+ if (!stopping)
160
+ options.store.reply(snapshot.id, "Cannot load sessions. Try /list again.");
161
+ return;
162
+ }
163
+ if (!stopping)
164
+ options.store.reply(snapshot.id, `Directory: ${snapshot.cwd}\n${sessions.length
165
+ ? sessions
166
+ .map((session) => `\n${session.id === snapshot.sessionId ? "[current] " : ""}${session.label}\n${session.id}`)
167
+ .join("\n")
168
+ : "No sessions yet. Create one with /new [name]."}${sessions.length ? "\n\nUse /switch <full ID> to select a session." : ""}`);
169
+ };
170
+ return;
171
+ }
137
172
  case "new":
173
+ case "switch": {
138
174
  if (task)
139
175
  return "Wait for the task to finish, or use /stop first.";
140
- options.store.setSession(channel.id, channel.cwd);
141
- return `New session.\nDirectory: ${channel.cwd}${channel.paused ? "\nPaused. Send /resume to continue." : ""}`;
176
+ const { name, args } = message.content;
177
+ if (name === "switch" && (!args || /\s/u.test(args)))
178
+ return "Use /switch <full session ID>. Find the ID with /list.";
179
+ const snapshot = structuredClone(channel);
180
+ changingSession = true;
181
+ controlQuery = async () => {
182
+ try {
183
+ let session;
184
+ try {
185
+ session =
186
+ name === "new"
187
+ ? await options.agent.createSession({
188
+ cwd: snapshot.cwd,
189
+ name: args,
190
+ model: snapshot.model,
191
+ permissionMode: snapshot.permissionMode,
192
+ }, modelLoading.signal)
193
+ : await options.agent.readSession(snapshot.cwd, args, modelLoading.signal);
194
+ }
195
+ catch (error) {
196
+ if (!stopping)
197
+ options.store.reply(snapshot.id, error instanceof Error && error.message.startsWith("Codex ")
198
+ ? error.message
199
+ : "Cannot change sessions. Current session unchanged. Check /list before retrying.");
200
+ return;
201
+ }
202
+ if (!stopping)
203
+ options.store.setSession(snapshot.id, snapshot.cwd, session.id, `${name === "new" ? "New session" : "Switched session"}: ${session.label}\nID: ${session.id}\nDirectory: ${snapshot.cwd}`);
204
+ }
205
+ finally {
206
+ changingSession = false;
207
+ }
208
+ };
209
+ return;
210
+ }
142
211
  case "stop":
143
212
  options.store.setPaused(channel.id, true);
144
213
  if (!task || task.channelId !== channel.id)
@@ -153,43 +222,50 @@ export function createRelayService(options) {
153
222
  case "last": {
154
223
  const last = options.store.lastTask(channel.id);
155
224
  return last
156
- ? `Task #${last.id} ${statuses[last.status]}\n${last.result ?? "No result yet."}`
225
+ ? `Task #${last.id}${last.cron ? ` · Cron #${last.cron.id}` : ""} ${statuses[last.status]}\n${last.result ?? "No result yet."}`
157
226
  : "No tasks yet.";
158
227
  }
159
228
  default:
160
229
  return "Unknown command. See /help.";
161
230
  }
162
231
  }
163
- if (message.content.type !== "text")
164
- return "Text messages only. See /help.";
232
+ if (message.content.type !== "text" && message.content.type !== "attachments")
233
+ return "Send text, a photo, or a file. See /help.";
165
234
  if (channel.paused)
166
235
  return "Paused. Check /last, then /resume to send a new task.";
167
- if (!message.content.text.trim())
236
+ const attachments = message.content.type === "attachments" ? message.content.attachments : [];
237
+ if (attachments.some((attachment) => attachment.size !== null && attachment.size > maxAttachmentBytes))
238
+ return "Attachment exceeds the 20 MB download limit. Send a smaller file.";
239
+ if (!message.content.text.trim() && !attachments.length)
168
240
  return "Send a task description.";
169
241
  if (task) {
170
242
  if (task.channelId !== channel.id)
171
243
  return `Task #${task.id} is running in another chat. Message not sent.`;
172
244
  if (task.status === "cancelling")
173
245
  return "Task stopping. Wait for its result.";
174
- options.store.appendTaskInput(task.id, message.content.text);
246
+ options.store.appendTaskInput(task.id, message.content.text, attachments);
175
247
  return;
176
248
  }
177
- options.store.createTask(channel, message.content.text);
249
+ options.store.createTask(channel, message.content.text, attachments);
178
250
  });
179
- /** 只有入站去重记录保存成功后才查询;重复的 /status 不会重复入队。 */
180
- if (statusQuery)
181
- statusQueries.push(statusQuery);
251
+ /** 只有入站去重记录保存成功后才执行;重复事件不会重复创建或切换会话。 */
252
+ if (controlQuery)
253
+ controlQueries.push(controlQuery);
182
254
  /** 只有选择和停止状态落盘后才影响运行时;回调答复不承担提交职责。 */
183
- if (((message.content.type === "command" && message.content.name === "stop") ||
184
- (message.content.type === "interaction" && message.content.action === "stop")) &&
185
- options.store.activeTask()?.status === "cancelling")
186
- options.agent.stop();
255
+ if ((message.content.type === "command" && message.content.name === "stop") ||
256
+ (message.content.type === "interaction" && message.content.action === "stop")) {
257
+ const active = options.store.activeTask();
258
+ if (!active || active.status === "cancelling")
259
+ inputPreparation?.abort();
260
+ if (active?.status === "cancelling")
261
+ options.agent.stop();
262
+ }
187
263
  return reply;
188
264
  },
189
- /** 慢模型列表请求不占用入站或任务循环;迟到结果由菜单 ID 校验丢弃。 */
265
+ /** 模型、状态和会话请求不占用入站或任务循环,关停后丢弃迟到结果。 */
190
266
  async loadModels(source) {
191
267
  while (!stopping) {
192
- const query = statusQueries.shift();
268
+ const query = controlQueries.shift();
193
269
  if (query) {
194
270
  await query();
195
271
  continue;
@@ -210,6 +286,16 @@ export function createRelayService(options) {
210
286
  options.store.fillModels(menu.channelId, menu.id, models);
211
287
  }
212
288
  },
289
+ /** 调度独立于执行和菜单查询;启动先跳过离线时段,再按持久化时间触发。 */
290
+ async schedule(source) {
291
+ if (stopping)
292
+ return;
293
+ options.store.recoverCrons(source);
294
+ while (!stopping) {
295
+ options.store.tickCrons(source);
296
+ await setTimeout(1_000);
297
+ }
298
+ },
213
299
  /** 领取任务后再次检查目录;运行结果与最终回复在同一个快照中保存。 */
214
300
  async run(source) {
215
301
  while (!stopping) {
@@ -229,11 +315,42 @@ export function createRelayService(options) {
229
315
  });
230
316
  continue;
231
317
  }
318
+ const preparing = new AbortController();
319
+ inputPreparation = preparing;
320
+ let attachments = [];
321
+ if (task.status === "preparing") {
322
+ try {
323
+ attachments = await options.prepareAttachments(task.attachments, preparing.signal);
324
+ if (stopping)
325
+ return;
326
+ if (preparing.signal.aborted || !options.store.markDispatching(task.id))
327
+ continue;
328
+ }
329
+ catch (error) {
330
+ if (stopping)
331
+ return;
332
+ options.store.finishTask(task.id, {
333
+ status: preparing.signal.aborted ? "cancelled" : "failed",
334
+ text: preparing.signal.aborted
335
+ ? "Stopped before starting."
336
+ : error instanceof AttachmentError
337
+ ? error.message
338
+ : "Cannot prepare attachments. Resend them to try again.",
339
+ });
340
+ continue;
341
+ }
342
+ finally {
343
+ if (inputPreparation === preparing)
344
+ inputPreparation = undefined;
345
+ }
346
+ }
347
+ inputPreparation = preparing;
232
348
  let finished = false;
233
349
  const running = options.agent
234
350
  .run({
235
351
  cwd: task.cwd,
236
352
  prompt: task.prompt,
353
+ attachments,
237
354
  sessionId: task.sessionId,
238
355
  runId: task.runId,
239
356
  permissionMode: task.permissionMode,
@@ -251,6 +368,7 @@ export function createRelayService(options) {
251
368
  })
252
369
  .finally(() => {
253
370
  finished = true;
371
+ preparing.abort();
254
372
  });
255
373
  /** 补充输入独立于入站处理和任务完成等待,慢 RPC 不阻塞 /status 与 /stop。 */
256
374
  const steering = (async () => {
@@ -262,11 +380,19 @@ export function createRelayService(options) {
262
380
  }
263
381
  let error;
264
382
  try {
265
- await options.agent.steer(input.text);
383
+ const attachments = await options.prepareAttachments(input.attachments, preparing.signal);
384
+ if (finished || stopping)
385
+ return;
386
+ if (!options.store.markTaskInputSending(task.id, input.index))
387
+ continue;
388
+ await options.agent.steer(input.text, attachments);
266
389
  }
267
390
  catch (cause) {
391
+ if (finished || stopping)
392
+ return;
268
393
  error =
269
- cause instanceof Error && cause.message.startsWith("Codex ")
394
+ cause instanceof AttachmentError ||
395
+ (cause instanceof Error && cause.message.startsWith("Codex "))
270
396
  ? cause.message
271
397
  : "Submission unconfirmed. Not retried; check the session before resending.";
272
398
  }
@@ -278,7 +404,7 @@ export function createRelayService(options) {
278
404
  while (!finished && !stopping) {
279
405
  const interaction = options.store.claimInteraction(task.id);
280
406
  if (interaction) {
281
- const submitted = options.agent.respond(interaction.id, interaction.answer);
407
+ const submitted = await options.agent.respond(interaction.id, interaction.answer);
282
408
  options.store.closeInteraction(interaction.id, submitted
283
409
  ? "Submitted. Waiting for results."
284
410
  : "Request closed or delivery unconfirmed. Check /status.");
@@ -300,12 +426,18 @@ export function createRelayService(options) {
300
426
  });
301
427
  throw error;
302
428
  }
429
+ finally {
430
+ preparing.abort();
431
+ if (inputPreparation === preparing)
432
+ inputPreparation = undefined;
433
+ }
303
434
  }
304
435
  },
305
436
  /** 服务退出只断开客户端;后台任务继续运行,重启后按已保存轮次接回。 */
306
437
  stop() {
307
438
  stopping = true;
308
439
  modelLoading.abort();
440
+ inputPreparation?.abort();
309
441
  options.agent.disconnect();
310
442
  },
311
443
  };