@emqo/claudebridge 0.5.2 → 0.6.0

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.
@@ -242,6 +242,13 @@ export class DiscordAdapter {
242
242
  const res = this.maxParallel > 1
243
243
  ? await this.engine.runParallel(task.user_id, task.description, "discord", task.chat_id)
244
244
  : await this.engine.runStream(task.user_id, task.description, "discord", task.chat_id);
245
+ if (res.timedOut) {
246
+ this.store.markTaskResult(task.id, "failed");
247
+ if (res.text)
248
+ this.store.setTaskResult(task.id, res.text.slice(0, 10000));
249
+ await channel.send(t(this.locale, "auto_failed", { id: task.id, err: "timed out" }));
250
+ return;
251
+ }
245
252
  this.store.markTaskResult(task.id, "done");
246
253
  if (res.text)
247
254
  this.store.setTaskResult(task.id, res.text.slice(0, 10000));
@@ -297,7 +304,12 @@ export class DiscordAdapter {
297
304
  };
298
305
  const lines = recent.map(task => {
299
306
  const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
300
- return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
307
+ let schedInfo = "";
308
+ if (task.status === "auto" && task.scheduled_at && task.scheduled_at > Date.now()) {
309
+ const mins = Math.ceil((task.scheduled_at - Date.now()) / 60000);
310
+ schedInfo = ` [in ${mins}min]`;
311
+ }
312
+ return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}]${schedInfo} ${task.description.slice(0, 60)}${chain}`;
301
313
  });
302
314
  const stats = this.store.getAutoTaskStats();
303
315
  const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
@@ -396,14 +396,28 @@ export class TelegramAdapter {
396
396
  const res = this.maxParallel > 1
397
397
  ? await this.engine.runParallel(task.user_id, task.description, "telegram", task.chat_id)
398
398
  : await this.engine.runStream(task.user_id, task.description, "telegram", task.chat_id);
399
+ if (res.timedOut) {
400
+ this.store.markTaskResult(task.id, "failed");
401
+ if (res.text)
402
+ this.store.setTaskResult(task.id, res.text.slice(0, 10000));
403
+ await this.reply(chatId, t(this.locale, "auto_failed", { id: task.id, err: "timed out" }));
404
+ return;
405
+ }
399
406
  this.store.markTaskResult(task.id, "done");
400
407
  if (res.text)
401
408
  this.store.setTaskResult(task.id, res.text.slice(0, 10000));
402
409
  const maxLen = this.config.chunk_size || 4000;
403
- const chunks = chunkText(res.text || "(no output)", maxLen);
410
+ const rawChunks = chunkText(res.text || "(no output)", maxLen);
411
+ const mdChunks = chunkText(toTelegramMarkdown(res.text || "(no output)"), maxLen);
404
412
  await this.reply(chatId, t(this.locale, "auto_done", { id: task.id, cost: (res.cost || 0).toFixed(4) }));
405
- for (const c of chunks)
406
- await this.reply(chatId, c);
413
+ for (let i = 0; i < mdChunks.length; i++) {
414
+ try {
415
+ await this.call("sendMessage", { chat_id: chatId, text: mdChunks[i], parse_mode: "MarkdownV2" });
416
+ }
417
+ catch {
418
+ await this.reply(chatId, rawChunks[i] || mdChunks[i]);
419
+ }
420
+ }
407
421
  // Chain progress reporting
408
422
  if (task.parent_id) {
409
423
  const progress = this.store.getChainProgress(task.parent_id);
@@ -501,7 +515,12 @@ export class TelegramAdapter {
501
515
  };
502
516
  const lines = recent.map(task => {
503
517
  const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
504
- return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
518
+ let schedInfo = "";
519
+ if (task.status === "auto" && task.scheduled_at && task.scheduled_at > Date.now()) {
520
+ const mins = Math.ceil((task.scheduled_at - Date.now()) / 60000);
521
+ schedInfo = ` [in ${mins}min]`;
522
+ }
523
+ return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}]${schedInfo} ${task.description.slice(0, 60)}${chain}`;
505
524
  });
506
525
  const stats = this.store.getAutoTaskStats();
507
526
  const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
@@ -6,6 +6,7 @@ export interface AgentResponse {
6
6
  text: string;
7
7
  sessionId: string;
8
8
  cost?: number;
9
+ timedOut?: boolean;
9
10
  }
10
11
  export type StreamCallback = (chunk: string, full: string) => void | Promise<void>;
11
12
  export declare class AgentEngine {
@@ -150,7 +150,7 @@ export class AgentEngine {
150
150
  const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
151
151
  child.stdin.end();
152
152
  console.log(`[agent] spawned claude pid=${child.pid} cwd=${cwd} args=${args.join(" ")}`);
153
- const timeoutMs = (this.config.agent.timeout_seconds || 300) * 1000;
153
+ const timeoutMs = (this.config.agent.timeout_seconds || 600) * 1000;
154
154
  const timer = setTimeout(() => { try {
155
155
  child.kill("SIGTERM");
156
156
  }
@@ -205,9 +205,10 @@ export class AgentEngine {
205
205
  clearTimeout(timer);
206
206
  console.log(`[agent] claude exited code=${code} signal=${signal} fullText=${fullText.length}chars stderr=${stderr.slice(0, 200)}`);
207
207
  if (signal === "SIGTERM") {
208
+ console.warn(`[agent] claude timed out after ${timeoutMs / 1000}s`);
208
209
  if (newSessionId)
209
210
  this.store.setSession(userId, newSessionId, platform);
210
- resolve({ text: fullText.trim() || "(timed out)", sessionId: newSessionId, cost });
211
+ resolve({ text: fullText.trim() || "(timed out)", sessionId: newSessionId, cost, timedOut: true });
211
212
  return;
212
213
  }
213
214
  if (code === 0 || fullText.trim()) {
@@ -256,7 +257,7 @@ export class AgentEngine {
256
257
  const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
257
258
  child.stdin.end();
258
259
  console.log(`[agent] spawned claude (parallel) pid=${child.pid} cwd=${cwd}`);
259
- const timeoutMs = (this.config.agent.timeout_seconds || 300) * 1000;
260
+ const timeoutMs = (this.config.agent.timeout_seconds || 600) * 1000;
260
261
  const timer = setTimeout(() => { try {
261
262
  child.kill("SIGTERM");
262
263
  }
@@ -301,8 +302,12 @@ export class AgentEngine {
301
302
  child.on("close", (code, signal) => {
302
303
  clearTimeout(timer);
303
304
  console.log(`[agent] claude (parallel) exited code=${code} signal=${signal} text=${fullText.length}chars`);
304
- if (code === 0 || fullText.trim() || signal === "SIGTERM") {
305
- resolve({ text: fullText.trim() || (signal === "SIGTERM" ? "(timed out)" : "(no response)"), sessionId, cost });
305
+ if (signal === "SIGTERM") {
306
+ console.warn(`[agent] claude (parallel) timed out after ${timeoutMs / 1000}s`);
307
+ resolve({ text: fullText.trim() || "(timed out)", sessionId, cost, timedOut: true });
308
+ }
309
+ else if (code === 0 || fullText.trim()) {
310
+ resolve({ text: fullText.trim() || "(no response)", sessionId, cost });
306
311
  }
307
312
  else {
308
313
  reject(new Error(`claude exited ${code}: ${stderr.slice(0, 500)}`));
package/dist/core/i18n.js CHANGED
@@ -11,6 +11,7 @@ const messages = {
11
11
  upload_failed: "Upload failed: ",
12
12
  reminder_notify: "Reminder: {desc}",
13
13
  auto_starting: "Auto task #{id} starting:\n{desc}",
14
+ auto_scheduled: "Auto task #{id} scheduled (executes in {minutes} min):\n{desc}",
14
15
  auto_done: "Auto task #{id} done (cost: ${cost}):",
15
16
  auto_failed: "Auto task #{id} failed: {err}",
16
17
  page_expired: "Page expired. Please resend your question.",
@@ -34,6 +35,7 @@ const messages = {
34
35
  upload_failed: "上传失败:",
35
36
  reminder_notify: "提醒:{desc}",
36
37
  auto_starting: "自动任务 #{id} 开始执行:\n{desc}",
38
+ auto_scheduled: "自动任务 #{id} 已排程({minutes} 分钟后执行):\n{desc}",
37
39
  auto_done: "自动任务 #{id} 完成(花费:${cost}):",
38
40
  auto_failed: "自动任务 #{id} 失败:{err}",
39
41
  page_expired: "页面已过期,请重新发送问题。",
@@ -30,7 +30,7 @@ export declare class Store {
30
30
  }[];
31
31
  clearMemories(userId: string): void;
32
32
  trimMemories(userId: string, max: number): void;
33
- addTask(userId: string, platform: string, chatId: string, description: string, remindAt?: number, auto?: boolean, parentId?: number): number;
33
+ addTask(userId: string, platform: string, chatId: string, description: string, remindAt?: number, auto?: boolean, parentId?: number, scheduledAt?: number): number;
34
34
  getTasks(userId: string): {
35
35
  id: number;
36
36
  description: string;
@@ -60,9 +60,10 @@ export declare class Store {
60
60
  id: number;
61
61
  description: string;
62
62
  status: string;
63
+ scheduled_at: number | null;
63
64
  created_at: number;
64
65
  }[];
65
- addApprovalTask(userId: string, platform: string, chatId: string, description: string, parentId?: number): number;
66
+ addApprovalTask(userId: string, platform: string, chatId: string, description: string, parentId?: number, scheduledAt?: number): number;
66
67
  getPendingApprovals(platform: string): {
67
68
  id: number;
68
69
  user_id: string;
@@ -104,6 +105,7 @@ export declare class Store {
104
105
  description: string;
105
106
  status: string;
106
107
  parent_id: number | null;
108
+ scheduled_at: number | null;
107
109
  created_at: number;
108
110
  }[];
109
111
  }
@@ -55,7 +55,7 @@ export class Store {
55
55
  CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id);
56
56
  CREATE INDEX IF NOT EXISTS idx_tasks_user ON tasks(user_id, status);
57
57
  `);
58
- // Schema migration: add parent_id and result columns
58
+ // Schema migration: add parent_id, result, and scheduled_at columns
59
59
  try {
60
60
  this.db.exec("ALTER TABLE tasks ADD COLUMN parent_id INTEGER");
61
61
  }
@@ -64,6 +64,10 @@ export class Store {
64
64
  this.db.exec("ALTER TABLE tasks ADD COLUMN result TEXT");
65
65
  }
66
66
  catch { }
67
+ try {
68
+ this.db.exec("ALTER TABLE tasks ADD COLUMN scheduled_at INTEGER");
69
+ }
70
+ catch { }
67
71
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id)");
68
72
  }
69
73
  // --- sessions ---
@@ -130,8 +134,8 @@ export class Store {
130
134
  this.db.prepare("DELETE FROM memories WHERE user_id = ? AND id NOT IN (SELECT id FROM memories WHERE user_id = ? ORDER BY created_at DESC LIMIT ?)").run(userId, userId, max);
131
135
  }
132
136
  // --- tasks ---
133
- addTask(userId, platform, chatId, description, remindAt, auto = false, parentId) {
134
- const r = this.db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, remind_at, parent_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(userId, platform, chatId, description, auto ? "auto" : "pending", remindAt ?? null, parentId ?? null, Date.now());
137
+ addTask(userId, platform, chatId, description, remindAt, auto = false, parentId, scheduledAt) {
138
+ const r = this.db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, remind_at, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(userId, platform, chatId, description, auto ? "auto" : "pending", remindAt ?? null, parentId ?? null, scheduledAt ?? null, Date.now());
135
139
  return Number(r.lastInsertRowid);
136
140
  }
137
141
  getTasks(userId) {
@@ -148,10 +152,11 @@ export class Store {
148
152
  this.db.prepare("UPDATE tasks SET reminder_sent = 1 WHERE id = ?").run(taskId);
149
153
  }
150
154
  getNextAutoTask(platform) {
155
+ const now = Date.now();
151
156
  if (platform) {
152
- return this.db.prepare("SELECT id, user_id, platform, chat_id, description FROM tasks WHERE status = 'auto' AND platform = ? ORDER BY created_at ASC LIMIT 1").get(platform) ?? null;
157
+ return this.db.prepare("SELECT id, user_id, platform, chat_id, description FROM tasks WHERE status = 'auto' AND platform = ? AND (scheduled_at IS NULL OR scheduled_at <= ?) ORDER BY created_at ASC LIMIT 1").get(platform, now) ?? null;
153
158
  }
154
- return this.db.prepare("SELECT id, user_id, platform, chat_id, description FROM tasks WHERE status = 'auto' ORDER BY created_at ASC LIMIT 1").get() ?? null;
159
+ return this.db.prepare("SELECT id, user_id, platform, chat_id, description FROM tasks WHERE status = 'auto' AND (scheduled_at IS NULL OR scheduled_at <= ?) ORDER BY created_at ASC LIMIT 1").get(now) ?? null;
155
160
  }
156
161
  markTaskRunning(taskId) {
157
162
  this.db.prepare("UPDATE tasks SET status = 'running' WHERE id = ?").run(taskId);
@@ -160,11 +165,11 @@ export class Store {
160
165
  this.db.prepare("UPDATE tasks SET status = ? WHERE id = ?").run(status, taskId);
161
166
  }
162
167
  getAutoTasks(userId) {
163
- return this.db.prepare("SELECT id, description, status, created_at FROM tasks WHERE user_id = ? AND status IN ('auto','running') ORDER BY created_at DESC").all(userId);
168
+ return this.db.prepare("SELECT id, description, status, scheduled_at, created_at FROM tasks WHERE user_id = ? AND status IN ('auto','running') ORDER BY created_at DESC").all(userId);
164
169
  }
165
170
  // --- HITL (Human-in-the-Loop) ---
166
- addApprovalTask(userId, platform, chatId, description, parentId) {
167
- const r = this.db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?)").run(userId, platform, chatId, description, parentId ?? null, Date.now());
171
+ addApprovalTask(userId, platform, chatId, description, parentId, scheduledAt) {
172
+ const r = this.db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?, ?)").run(userId, platform, chatId, description, parentId ?? null, scheduledAt ?? null, Date.now());
168
173
  return Number(r.lastInsertRowid);
169
174
  }
170
175
  getPendingApprovals(platform) {
@@ -187,7 +192,7 @@ export class Store {
187
192
  }
188
193
  // --- Parallel ---
189
194
  getNextAutoTasks(platform, limit) {
190
- return this.db.prepare("SELECT id, user_id, platform, chat_id, description, parent_id FROM tasks WHERE status = 'auto' AND platform = ? ORDER BY created_at ASC LIMIT ?").all(platform, limit);
195
+ return this.db.prepare("SELECT id, user_id, platform, chat_id, description, parent_id FROM tasks WHERE status = 'auto' AND platform = ? AND (scheduled_at IS NULL OR scheduled_at <= ?) ORDER BY created_at ASC LIMIT ?").all(platform, Date.now(), limit);
191
196
  }
192
197
  // --- Observability ---
193
198
  getAutoTaskStats(userId) {
@@ -211,6 +216,6 @@ export class Store {
211
216
  return result;
212
217
  }
213
218
  getRecentAutoTasks(platform, limit) {
214
- return this.db.prepare("SELECT id, user_id, description, status, parent_id, created_at FROM tasks WHERE platform = ? AND status IN ('auto','running','done','failed','approval_pending','cancelled') ORDER BY created_at DESC LIMIT ?").all(platform, limit);
219
+ return this.db.prepare("SELECT id, user_id, description, status, parent_id, scheduled_at, created_at FROM tasks WHERE platform = ? AND status IN ('auto','running','done','failed','approval_pending','cancelled') ORDER BY created_at DESC LIMIT ?").all(platform, limit);
215
220
  }
216
221
  }
package/dist/ctl.js CHANGED
@@ -109,23 +109,39 @@ else if (category === "auto") {
109
109
  parentId = parseInt(descParts[parentIdx + 1]);
110
110
  descParts.splice(parentIdx, 2);
111
111
  }
112
+ // Parse optional --delay flag
113
+ let scheduledAt = null;
114
+ const delayIdx = descParts.indexOf("--delay");
115
+ if (delayIdx !== -1 && descParts[delayIdx + 1]) {
116
+ const delayMin = parseInt(descParts[delayIdx + 1]);
117
+ scheduledAt = Date.now() + delayMin * 60000;
118
+ descParts.splice(delayIdx, 2);
119
+ }
112
120
  const desc = descParts.join(" ");
113
- const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, created_at) VALUES (?, ?, ?, ?, 'auto', ?, ?)").run(userId, platform, chatId, desc, parentId, Date.now());
114
- output({ ok: true, id: Number(r.lastInsertRowid), message: "Auto task queued" });
121
+ const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, 'auto', ?, ?, ?)").run(userId, platform, chatId, desc, parentId, scheduledAt, Date.now());
122
+ output({ ok: true, id: Number(r.lastInsertRowid), scheduled_at: scheduledAt, message: scheduledAt ? `Auto task scheduled (in ${Math.ceil((scheduledAt - Date.now()) / 60000)} min)` : "Auto task queued" });
115
123
  }
116
124
  else if (action === "add-approval") {
117
125
  const [userId, platform, chatId, ...descParts] = rest;
118
126
  if (!userId || !platform || !chatId || !descParts.length)
119
- fail("Usage: auto add-approval <user_id> <platform> <chat_id> <description> [--parent <id>]");
127
+ fail("Usage: auto add-approval <user_id> <platform> <chat_id> <description> [--parent <id>] [--delay <minutes>]");
120
128
  let parentId = null;
121
129
  const parentIdx = descParts.indexOf("--parent");
122
130
  if (parentIdx !== -1 && descParts[parentIdx + 1]) {
123
131
  parentId = parseInt(descParts[parentIdx + 1]);
124
132
  descParts.splice(parentIdx, 2);
125
133
  }
134
+ // Parse optional --delay flag
135
+ let scheduledAt = null;
136
+ const delayIdx = descParts.indexOf("--delay");
137
+ if (delayIdx !== -1 && descParts[delayIdx + 1]) {
138
+ const delayMin = parseInt(descParts[delayIdx + 1]);
139
+ scheduledAt = Date.now() + delayMin * 60000;
140
+ descParts.splice(delayIdx, 2);
141
+ }
126
142
  const desc = descParts.join(" ");
127
- const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?)").run(userId, platform, chatId, desc, parentId, Date.now());
128
- output({ ok: true, id: Number(r.lastInsertRowid), message: "Auto task queued for approval" });
143
+ const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?, ?)").run(userId, platform, chatId, desc, parentId, scheduledAt, Date.now());
144
+ output({ ok: true, id: Number(r.lastInsertRowid), scheduled_at: scheduledAt, message: scheduledAt ? `Auto task queued for approval (scheduled in ${Math.ceil((scheduledAt - Date.now()) / 60000)} min)` : "Auto task queued for approval" });
129
145
  }
130
146
  else if (action === "result") {
131
147
  const [taskId, ...resultParts] = rest;
@@ -139,7 +155,7 @@ else if (category === "auto") {
139
155
  const [userId] = rest;
140
156
  if (!userId)
141
157
  fail("Usage: auto list <user_id>");
142
- const rows = db.prepare("SELECT id, description, status, created_at FROM tasks WHERE user_id = ? AND status IN ('auto','running') ORDER BY created_at DESC").all(userId);
158
+ const rows = db.prepare("SELECT id, description, status, scheduled_at, created_at FROM tasks WHERE user_id = ? AND status IN ('auto','running') ORDER BY created_at DESC").all(userId);
143
159
  output({ ok: true, tasks: rows });
144
160
  }
145
161
  else if (action === "cancel") {
@@ -30,6 +30,7 @@ export function generateSkillDoc(ctx) {
30
30
  ``,
31
31
  `### 自动任务`,
32
32
  `- 创建自动任务: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "任务描述"\``,
33
+ `- 创建定时自动任务: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "任务描述" --delay <分钟数>\``,
33
34
  `- 查看自动任务: \`${ctl} auto list ${ctx.userId}\``,
34
35
  `- 取消自动任务: \`${ctl} auto cancel <任务ID>\``,
35
36
  ``,
@@ -75,6 +76,42 @@ export function generateSkillDoc(ctx) {
75
76
  ` 2. 任务#2: "修复N+1查询" (--parent 1)`,
76
77
  ` 3. 任务#3: "添加缓存层" (--parent 1)`,
77
78
  ` 4. 任务#4: "运行基准测试" (--parent 1)`,
79
+ ``,
80
+ `### 定时自动任务`,
81
+ `- 用 --delay <分钟数> 安排延迟执行: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "任务描述" --delay 30\``,
82
+ `- 任务创建后不会立即执行,等到延迟时间到达后才进入执行队列`,
83
+ `- 适用场景:需要等待、验证、或时间间隔的任务`,
84
+ `- 可与 --parent 组合使用: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "验证部署" --delay 15 --parent 42\``,
85
+ `- 审批任务也支持延迟: \`${ctl} auto add-approval ${ctx.userId} ${ctx.platform} ${ctx.chatId} "描述" --delay 60\``,
86
+ ``,
87
+ `### 自主项目管理模式`,
88
+ `当用户委托你全程管理一个项目时,使用以下模式:`,
89
+ ``,
90
+ `**模式1:部署验证**`,
91
+ `- 任务1: "部署代码到staging"`,
92
+ `- 任务2 (--delay 15): "检查staging健康状态:访问/health端点,查看最近日志是否有错误"`,
93
+ `- 任务3 (--delay 30): "如果staging正常则部署到生产环境,否则回滚并报告"`,
94
+ ``,
95
+ `**模式2:迭代开发**`,
96
+ `- 任务1: "分析项目,识别所有需要修改的点,保存到记忆"`,
97
+ `- 任务2 (--delay 2): "读取记忆,执行第一批修改,运行测试"`,
98
+ `- 任务3 (--delay 5): "检查测试结果,修复失败用例"`,
99
+ `- 任务4 (--delay 3): "运行完整集成测试,生成报告"`,
100
+ ``,
101
+ `**模式3:监控巡检**`,
102
+ `- 任务1: "检查应用状态:CPU、内存、错误率"`,
103
+ `- 任务2 (--delay 60): "再次检查应用状态,与上次对比,如有恶化则创建告警任务"`,
104
+ ``,
105
+ `**模式4:数据库迁移**`,
106
+ `- 任务1: "执行数据库迁移脚本"`,
107
+ `- 任务2 (--delay 30): "验证数据完整性:检查行数、约束、索引"`,
108
+ ``,
109
+ `**关键原则:**`,
110
+ `- 需要等待验证的步骤必须用 --delay,不要立即执行`,
111
+ `- 每个任务描述必须完整自包含(全新会话执行)`,
112
+ `- 用 memory add 传递上下文,用 --parent 关联链路`,
113
+ `- 用 add-approval 保护破坏性操作(删除、部署、迁移)`,
114
+ `- 任务描述中加入"先用 memory list 读取前序任务结论"确保上下文`,
78
115
  ].join("\n");
79
116
  }
80
117
  return [
@@ -101,6 +138,7 @@ export function generateSkillDoc(ctx) {
101
138
  ``,
102
139
  `### Auto Tasks`,
103
140
  `- Queue an auto task: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "description"\``,
141
+ `- Schedule a delayed auto task: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "description" --delay <minutes>\``,
104
142
  `- List auto tasks: \`${ctl} auto list ${ctx.userId}\``,
105
143
  `- Cancel an auto task: \`${ctl} auto cancel <task_id>\``,
106
144
  ``,
@@ -146,5 +184,41 @@ export function generateSkillDoc(ctx) {
146
184
  ` 2. Task #2: "Fix N+1 queries" (--parent 1)`,
147
185
  ` 3. Task #3: "Add caching layer" (--parent 1)`,
148
186
  ` 4. Task #4: "Run benchmarks" (--parent 1)`,
187
+ ``,
188
+ `### Scheduled Auto Tasks`,
189
+ `- Use --delay <minutes> to schedule delayed execution: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "description" --delay 30\``,
190
+ `- The task won't execute immediately -- it enters the queue only after the delay expires`,
191
+ `- Use cases: tasks that need waiting, verification, or time gaps between steps`,
192
+ `- Can combine with --parent: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "verify deploy" --delay 15 --parent 42\``,
193
+ `- Approval tasks also support delay: \`${ctl} auto add-approval ${ctx.userId} ${ctx.platform} ${ctx.chatId} "desc" --delay 60\``,
194
+ ``,
195
+ `### Autonomous Project Management Patterns`,
196
+ `When the user delegates full project management to you, use these patterns:`,
197
+ ``,
198
+ `**Pattern 1: Deploy & Verify**`,
199
+ `- Task 1: "Deploy code to staging"`,
200
+ `- Task 2 (--delay 15): "Check staging health: hit /health endpoint, review recent logs for errors"`,
201
+ `- Task 3 (--delay 30): "If staging is healthy, deploy to production. Otherwise rollback and report."`,
202
+ ``,
203
+ `**Pattern 2: Iterative Development**`,
204
+ `- Task 1: "Analyze project, identify all changes needed, save findings to memory"`,
205
+ `- Task 2 (--delay 2): "Read memory, execute first batch of changes, run tests"`,
206
+ `- Task 3 (--delay 5): "Check test results, fix any failures"`,
207
+ `- Task 4 (--delay 3): "Run full integration tests, generate report"`,
208
+ ``,
209
+ `**Pattern 3: Monitoring / Health Check**`,
210
+ `- Task 1: "Check application status: CPU, memory, error rates"`,
211
+ `- Task 2 (--delay 60): "Re-check application status, compare with previous check, create alert tasks if degraded"`,
212
+ ``,
213
+ `**Pattern 4: Database Migration**`,
214
+ `- Task 1: "Run database migration script"`,
215
+ `- Task 2 (--delay 30): "Verify data integrity: check row counts, constraints, indexes"`,
216
+ ``,
217
+ `**Key principles:**`,
218
+ `- Steps that need wait/verification MUST use --delay, don't execute immediately`,
219
+ `- Each task description must be self-contained (runs in a fresh session)`,
220
+ `- Use memory add to pass context, use --parent to link chains`,
221
+ `- Use add-approval for destructive operations (delete, deploy, migrate)`,
222
+ `- Include "first run memory list to review prior task conclusions" in descriptions`,
149
223
  ].join("\n");
150
224
  }
package/dist/webhook.js CHANGED
@@ -60,13 +60,14 @@ export class WebhookServer {
60
60
  return;
61
61
  }
62
62
  let id;
63
+ const scheduledAt = data.delay_minutes ? Date.now() + data.delay_minutes * 60000 : undefined;
63
64
  if (data.approval) {
64
- id = this.store.addApprovalTask(data.user_id, data.platform, data.chat_id, data.description, data.parent_id);
65
+ id = this.store.addApprovalTask(data.user_id, data.platform, data.chat_id, data.description, data.parent_id, scheduledAt);
65
66
  }
66
67
  else {
67
- id = this.store.addTask(data.user_id, data.platform, data.chat_id, data.description, undefined, true, data.parent_id);
68
+ id = this.store.addTask(data.user_id, data.platform, data.chat_id, data.description, undefined, true, data.parent_id, scheduledAt);
68
69
  }
69
- this.json(res, 201, { ok: true, id, status: data.approval ? "approval_pending" : "auto" });
70
+ this.json(res, 201, { ok: true, id, status: data.approval ? "approval_pending" : "auto", scheduled_at: scheduledAt || null });
70
71
  }
71
72
  catch (e) {
72
73
  this.json(res, 400, { error: e.message || "Invalid JSON" });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emqo/claudebridge",
3
- "version": "0.5.2",
4
- "description": "Bridge claude CLI to chat platforms (Telegram, Discord) with HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
3
+ "version": "0.6.0",
4
+ "description": "Bridge claude CLI to chat platforms (Telegram, Discord) with scheduled auto-tasks, autonomous project management, HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "claudebridge": "dist/cli.js",