flashclaw 1.7.1 → 1.9.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.
Files changed (86) hide show
  1. package/README.md +115 -45
  2. package/dist/agent-registry.d.ts.map +1 -0
  3. package/dist/agent-registry.js.map +1 -0
  4. package/dist/agent-runner.d.ts +2 -0
  5. package/dist/agent-runner.d.ts.map +1 -1
  6. package/dist/agent-runner.js +280 -36
  7. package/dist/agent-runner.js.map +1 -1
  8. package/dist/channel-manager.d.ts.map +1 -1
  9. package/dist/channel-manager.js +17 -9
  10. package/dist/channel-manager.js.map +1 -1
  11. package/dist/cli-ink.d.ts +11 -0
  12. package/dist/cli-ink.d.ts.map +1 -0
  13. package/dist/cli-ink.js +495 -0
  14. package/dist/cli-ink.js.map +1 -0
  15. package/dist/cli.js +18 -355
  16. package/dist/cli.js.map +1 -1
  17. package/dist/commands.d.ts +2 -0
  18. package/dist/commands.d.ts.map +1 -1
  19. package/dist/commands.js +92 -4
  20. package/dist/commands.js.map +1 -1
  21. package/dist/config.js +1 -1
  22. package/dist/config.js.map +1 -1
  23. package/dist/core/context-guard.d.ts +3 -3
  24. package/dist/core/context-guard.d.ts.map +1 -1
  25. package/dist/core/context-guard.js +32 -18
  26. package/dist/core/context-guard.js.map +1 -1
  27. package/dist/core/memory.d.ts +54 -31
  28. package/dist/core/memory.d.ts.map +1 -1
  29. package/dist/core/memory.js +233 -97
  30. package/dist/core/memory.js.map +1 -1
  31. package/dist/core/model-capabilities.d.ts.map +1 -1
  32. package/dist/core/model-capabilities.js +4 -2
  33. package/dist/core/model-capabilities.js.map +1 -1
  34. package/dist/core-api.d.ts +117 -0
  35. package/dist/core-api.d.ts.map +1 -0
  36. package/dist/core-api.js +218 -0
  37. package/dist/core-api.js.map +1 -0
  38. package/dist/health.js +1 -1
  39. package/dist/health.js.map +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +160 -78
  42. package/dist/index.js.map +1 -1
  43. package/dist/message-queue.js +1 -1
  44. package/dist/message-queue.js.map +1 -1
  45. package/dist/paths.d.ts +4 -0
  46. package/dist/paths.d.ts.map +1 -1
  47. package/dist/paths.js +7 -0
  48. package/dist/paths.js.map +1 -1
  49. package/dist/plugins/loader.js +4 -4
  50. package/dist/plugins/loader.js.map +1 -1
  51. package/dist/plugins/manager.d.ts.map +1 -1
  52. package/dist/plugins/manager.js +34 -13
  53. package/dist/plugins/manager.js.map +1 -1
  54. package/dist/plugins/types.d.ts +3 -0
  55. package/dist/plugins/types.d.ts.map +1 -1
  56. package/dist/plugins/types.js.map +1 -1
  57. package/dist/soul-manager.d.ts +42 -0
  58. package/dist/soul-manager.d.ts.map +1 -0
  59. package/dist/soul-manager.js +203 -0
  60. package/dist/soul-manager.js.map +1 -0
  61. package/dist/task-scheduler.d.ts.map +1 -1
  62. package/dist/task-scheduler.js +41 -24
  63. package/dist/task-scheduler.js.map +1 -1
  64. package/dist/tool-params.d.ts +13 -0
  65. package/dist/tool-params.d.ts.map +1 -0
  66. package/dist/tool-params.js +239 -0
  67. package/dist/tool-params.js.map +1 -0
  68. package/dist/types.d.ts +33 -1
  69. package/dist/types.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/plugins/anthropic-provider/index.ts +188 -6
  72. package/plugins/memory/index.ts +68 -35
  73. package/plugins/cancel-task/index.ts +0 -161
  74. package/plugins/cancel-task/plugin.json +0 -9
  75. package/plugins/cli-channel/index.ts +0 -51
  76. package/plugins/cli-channel/plugin.json +0 -8
  77. package/plugins/list-tasks/index.ts +0 -150
  78. package/plugins/list-tasks/plugin.json +0 -9
  79. package/plugins/pause-task/index.ts +0 -95
  80. package/plugins/pause-task/plugin.json +0 -8
  81. package/plugins/register-group/index.ts +0 -147
  82. package/plugins/register-group/plugin.json +0 -7
  83. package/plugins/resume-task/index.ts +0 -92
  84. package/plugins/resume-task/plugin.json +0 -8
  85. package/plugins/schedule-task/index.ts +0 -248
  86. package/plugins/schedule-task/plugin.json +0 -9
@@ -14,18 +14,53 @@ import type {
14
14
  PluginConfig,
15
15
  ImageBlock,
16
16
  TextBlock,
17
+ ToolDefinition,
17
18
  } from '../../src/plugins/types';
18
19
 
19
20
  // ==================== 内部状态 ====================
20
21
 
21
22
  let client: Anthropic | null = null;
22
23
  let model: string = 'claude-sonnet-4-20250514';
24
+ let baseURL: string | undefined;
23
25
 
24
26
  // ==================== 内部常量和工具函数 ====================
25
27
 
26
28
  const MAX_TOOL_CALL_DEPTH = 20;
29
+
30
+ /**
31
+ * 从错误对象中提取完整的错误信息链(包括 cause)
32
+ * Anthropic SDK 的 APIConnectionError 会将真正的网络错误藏在 cause 中
33
+ */
34
+ function extractFullErrorMessage(err: unknown): string {
35
+ if (!(err instanceof Error)) return String(err);
36
+
37
+ const parts: string[] = [err.message];
38
+ let current: unknown = (err as Error & { cause?: unknown }).cause;
39
+ let depth = 0;
40
+
41
+ while (current && depth < 5) {
42
+ if (current instanceof Error) {
43
+ parts.push(current.message);
44
+ current = (current as Error & { cause?: unknown }).cause;
45
+ } else {
46
+ parts.push(String(current));
47
+ break;
48
+ }
49
+ depth++;
50
+ }
51
+
52
+ // 附加 HTTP 状态码(如果是 API 错误)
53
+ const status = (err as Error & { status?: number }).status;
54
+ if (status) {
55
+ parts.unshift(`[HTTP ${status}]`);
56
+ }
57
+
58
+ return parts.join(' → ');
59
+ }
27
60
  const MAX_TOOL_RESULT_CHARS = 4000;
28
61
  const KEEP_RECENT_TOOL_ROUNDS = 2;
62
+ const MOCK_RESPONSE_PREFIX = process.env.FLASHCLAW_MOCK_RESPONSE_PREFIX || 'MOCK';
63
+ const MOCK_TOOL_MARKER = process.env.FLASHCLAW_MOCK_TOOL_MARKER || '[tool:send_message]';
29
64
 
30
65
  function truncateToolResult(content: string, maxChars: number): string {
31
66
  if (content.length <= maxChars) return content;
@@ -102,6 +137,88 @@ function extractText(response: Anthropic.Message): string {
102
137
  return textBlocks.map(block => block.text).join('');
103
138
  }
104
139
 
140
+ function isMockMode(): boolean {
141
+ return process.env.FLASHCLAW_MOCK_API === '1';
142
+ }
143
+
144
+ function contentToText(content: ChatMessage['content']): string {
145
+ if (typeof content === 'string') return content;
146
+ return content
147
+ .filter((block): block is TextBlock => block.type === 'text')
148
+ .map(block => block.text)
149
+ .join('');
150
+ }
151
+
152
+ function getLastUserText(messages: ChatMessage[]): string {
153
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
154
+ const msg = messages[i];
155
+ if (msg.role === 'user') {
156
+ return contentToText(msg.content);
157
+ }
158
+ }
159
+ return '';
160
+ }
161
+
162
+ function shouldMockToolUse(prompt: string, tools?: ToolDefinition[]): boolean {
163
+ if (!tools || tools.length === 0) return false;
164
+ if (process.env.FLASHCLAW_MOCK_FORCE_TOOL === '1') return true;
165
+ return prompt.includes(MOCK_TOOL_MARKER);
166
+ }
167
+
168
+ function buildMockMessage(params: {
169
+ content: Array<TextBlock | { type: 'tool_use'; id: string; name: string; input: unknown }>;
170
+ stopReason: 'end_turn' | 'tool_use';
171
+ model: string;
172
+ }): Anthropic.Message {
173
+ return {
174
+ id: `mock-${Date.now()}`,
175
+ type: 'message',
176
+ role: 'assistant',
177
+ model: params.model,
178
+ content: params.content as Anthropic.Message['content'],
179
+ stop_reason: params.stopReason,
180
+ stop_sequence: null,
181
+ usage: {
182
+ input_tokens: 1,
183
+ output_tokens: 1,
184
+ cache_creation_input_tokens: null,
185
+ cache_read_input_tokens: null,
186
+ cache_creation: null,
187
+ server_tool_use: null,
188
+ service_tier: 'standard',
189
+ },
190
+ };
191
+ }
192
+
193
+ async function createMockResponse(messages: ChatMessage[], options?: ChatOptions): Promise<Anthropic.Message> {
194
+ const prompt = getLastUserText(messages);
195
+ const tools = options?.tools;
196
+ const useTool = shouldMockToolUse(prompt, tools);
197
+
198
+ if (useTool && tools && tools.length > 0) {
199
+ const toolName = tools.find(t => t.name === 'send_message')?.name || tools[0].name;
200
+ const toolInput = { content: `${MOCK_RESPONSE_PREFIX} TOOL: ${prompt}` };
201
+ return buildMockMessage({
202
+ model,
203
+ stopReason: 'tool_use',
204
+ content: [
205
+ {
206
+ type: 'tool_use',
207
+ id: 'mock_tool_1',
208
+ name: toolName,
209
+ input: toolInput,
210
+ },
211
+ ],
212
+ });
213
+ }
214
+
215
+ return buildMockMessage({
216
+ model,
217
+ stopReason: 'end_turn',
218
+ content: [{ type: 'text', text: `${MOCK_RESPONSE_PREFIX}: ${prompt}` }],
219
+ });
220
+ }
221
+
105
222
  async function streamFollowUp(
106
223
  messages: Anthropic.MessageParam[],
107
224
  options?: ChatOptions,
@@ -126,7 +243,13 @@ async function streamFollowUp(
126
243
  params.tools = options.tools as unknown as Anthropic.Tool[];
127
244
  }
128
245
 
129
- const stream = await client.messages.create(params);
246
+ let stream;
247
+ try {
248
+ stream = await client.messages.create(params);
249
+ } catch (err) {
250
+ const detail = extractFullErrorMessage(err);
251
+ throw new Error(`Anthropic API 后续请求失败 (${baseURL || 'default'}): ${detail}`);
252
+ }
130
253
 
131
254
  let finalMessage: Anthropic.Message | null = null;
132
255
  const contentBlocks: Array<Anthropic.TextBlock | Anthropic.ToolUseBlock> = [];
@@ -274,25 +397,36 @@ const anthropicProvider: AIProviderPlugin = {
274
397
  description: 'Anthropic AI Provider - 支持 Claude 等模型',
275
398
 
276
399
  async init(config: PluginConfig): Promise<void> {
400
+ model = config.model as string || process.env.AI_MODEL || process.env.ANTHROPIC_MODEL || (isMockMode() ? 'mock-model' : 'claude-sonnet-4-20250514');
401
+
402
+ if (isMockMode()) {
403
+ client = null;
404
+ return;
405
+ }
406
+
277
407
  const apiKey = config.apiKey as string || process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
278
408
  if (!apiKey) {
279
409
  throw new Error('Missing API key: ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY');
280
410
  }
281
411
 
412
+ baseURL = config.baseURL as string || process.env.ANTHROPIC_BASE_URL || undefined;
413
+
282
414
  client = new Anthropic({
283
415
  apiKey,
284
- baseURL: config.baseURL as string || process.env.ANTHROPIC_BASE_URL,
416
+ baseURL,
285
417
  maxRetries: 0,
286
418
  timeout: config.timeout ? Number(config.timeout) : 60000,
287
419
  });
288
-
289
- model = config.model as string || process.env.AI_MODEL || process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514';
290
420
  },
291
421
 
292
422
  async chat(
293
423
  messages: ChatMessage[],
294
424
  options?: ChatOptions
295
425
  ): Promise<Anthropic.Message> {
426
+ if (isMockMode()) {
427
+ return createMockResponse(messages, options);
428
+ }
429
+
296
430
  if (!client) {
297
431
  throw new Error('Provider not initialized. Call init() first.');
298
432
  }
@@ -322,13 +456,36 @@ const anthropicProvider: AIProviderPlugin = {
322
456
  params.stop_sequences = options.stopSequences;
323
457
  }
324
458
 
325
- return await client.messages.create(params);
459
+ try {
460
+ return await client.messages.create(params);
461
+ } catch (err) {
462
+ const detail = extractFullErrorMessage(err);
463
+ throw new Error(`Anthropic API 请求失败 (${baseURL || 'default'}): ${detail}`);
464
+ }
326
465
  },
327
466
 
328
467
  async *chatStream(
329
468
  messages: ChatMessage[],
330
469
  options?: ChatOptions
331
470
  ): AsyncGenerator<StreamEvent> {
471
+ if (isMockMode()) {
472
+ const response = await createMockResponse(messages, options);
473
+ for (const block of response.content) {
474
+ if (block.type === 'text') {
475
+ yield { type: 'text', text: block.text };
476
+ } else if (block.type === 'tool_use') {
477
+ yield {
478
+ type: 'tool_use',
479
+ id: block.id,
480
+ name: block.name,
481
+ input: block.input,
482
+ };
483
+ }
484
+ }
485
+ yield { type: 'done', message: response };
486
+ return;
487
+ }
488
+
332
489
  if (!client) {
333
490
  throw new Error('Provider not initialized. Call init() first.');
334
491
  }
@@ -355,7 +512,13 @@ const anthropicProvider: AIProviderPlugin = {
355
512
  params.temperature = options.temperature;
356
513
  }
357
514
 
358
- const stream = await client.messages.create(params);
515
+ let stream;
516
+ try {
517
+ stream = await client.messages.create(params);
518
+ } catch (err) {
519
+ const detail = extractFullErrorMessage(err);
520
+ throw new Error(`Anthropic API 请求失败 (${baseURL || 'default'}): ${detail}`);
521
+ }
359
522
 
360
523
  let finalMessage: Anthropic.Message | null = null;
361
524
  const contentBlocks: Array<Anthropic.TextBlock | Anthropic.ToolUseBlock> = [];
@@ -441,6 +604,25 @@ const anthropicProvider: AIProviderPlugin = {
441
604
  heartbeat?: HeartbeatCallback
442
605
  ): Promise<string> {
443
606
  const anthropicResponse = response as Anthropic.Message;
607
+
608
+ if (isMockMode()) {
609
+ const toolUseBlocks = anthropicResponse.content.filter(
610
+ (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use'
611
+ );
612
+
613
+ if (toolUseBlocks.length === 0) {
614
+ return extractText(anthropicResponse);
615
+ }
616
+
617
+ const results: string[] = [];
618
+ for (const toolUse of toolUseBlocks) {
619
+ heartbeat?.();
620
+ const result = await executeTool(toolUse.name, toolUse.input);
621
+ results.push(typeof result === 'string' ? result : JSON.stringify(result));
622
+ }
623
+ return results.join('\n');
624
+ }
625
+
444
626
  const apiMessages: Anthropic.MessageParam[] = messages.map(msg => ({
445
627
  role: msg.role as 'user' | 'assistant',
446
628
  content: msg.content,
@@ -10,39 +10,44 @@ import { getMemoryManager } from '../../src/core/memory.js';
10
10
  * 记忆操作参数
11
11
  */
12
12
  interface MemoryParams {
13
- /** 操作类型:remember(记住)或 recall(回忆) */
14
- action: 'remember' | 'recall';
13
+ /** 操作类型:remember(记住)、recall(回忆)或 log(追加每日日志) */
14
+ action: 'remember' | 'recall' | 'log';
15
15
  /** 记忆键(remember 必需,recall 可选) */
16
16
  key?: string;
17
- /** 记忆值(remember 必需) */
17
+ /** 记忆值(remember 必需)/ 日志内容(log 必需) */
18
18
  value?: string;
19
- /** 作用域:user(用户级别,跨会话共享)或 group(会话级别,默认) */
20
- scope?: 'user' | 'group';
19
+ /** 作用域:user(用户级别)或 global(全局共享,默认) */
20
+ scope?: 'user' | 'global';
21
21
  }
22
22
 
23
23
  const plugin: ToolPlugin = {
24
24
  name: 'memory',
25
- version: '1.0.0',
26
- description: '长期记忆管理,可以记住和回忆重要信息',
25
+ version: '1.1.0',
26
+ description: '长期记忆管理,可以记住、回忆重要信息,以及写入每日日志',
27
27
 
28
28
  schema: {
29
29
  name: 'memory',
30
- description: `管理长期记忆。支持两种操作:
31
- - remember: 保存重要信息到长期记忆(用户偏好、重要事实等)
32
- - recall: 回忆之前保存的信息
30
+ description: `管理长期记忆和每日日志。
33
31
 
34
- 支持两种作用域:
35
- - user: 用户级别记忆,跨所有会话共享(推荐用于个人偏好)
36
- - group: 会话级别记忆,仅在当前会话有效(默认)
32
+ **何时用 remember**: 保存持久事实(姓名、偏好、配置等),需要 key 和 value
33
+ **何时用 recall**: 查询之前保存的事实
34
+ **何时用 log**: 记录事件、笔记、动态("今天做了XX"、"开了会"、"学了XX"),自动按日期归档,无需 key
37
35
 
38
- 记忆会持久化到文件,跨会话保持。`,
36
+ 示例:
37
+ - "记住我叫张三" → remember(key="user_name", value="用户叫张三")
38
+ - "你现在叫波特" → remember(key="ai_name", value="AI 的名字是波特")
39
+ - "我喜欢吃火锅" → remember(key="user_food", value="用户喜欢吃火锅")
40
+ - "帮我记录今天开了会" → log(value="今天开了会")
41
+ - "我叫什么" → recall(key="user_name")
42
+
43
+ key 命名建议:用 user_ 前缀表示用户信息,用 ai_ 前缀表示 AI 自身信息。`,
39
44
  input_schema: {
40
45
  type: 'object',
41
46
  properties: {
42
47
  action: {
43
48
  type: 'string',
44
- enum: ['remember', 'recall'],
45
- description: 'remember 保存信息,recall 回忆信息'
49
+ enum: ['remember', 'recall', 'log'],
50
+ description: 'remember 保存信息,recall 回忆信息,log 追加每日日志'
46
51
  },
47
52
  key: {
48
53
  type: 'string',
@@ -54,8 +59,8 @@ const plugin: ToolPlugin = {
54
59
  },
55
60
  scope: {
56
61
  type: 'string',
57
- enum: ['user', 'group'],
58
- description: '作用域。user=用户级别(跨会话共享,适合个人偏好),group=会话级别(默认)'
62
+ enum: ['user', 'global'],
63
+ description: '作用域。user=用户级别(适合个人偏好),global=全局级别(跨渠道共享,默认)'
59
64
  }
60
65
  },
61
66
  required: ['action']
@@ -63,12 +68,11 @@ const plugin: ToolPlugin = {
63
68
  },
64
69
 
65
70
  async execute(params: unknown, context: ToolContext): Promise<ToolResult> {
66
- const { action, key, value, scope = 'group' } = params as MemoryParams;
71
+ const { action, key, value, scope = 'global' } = params as MemoryParams;
67
72
  const mm = getMemoryManager();
68
-
69
- // 默认使用会话级别记忆(仅当前会话有效)
73
+
70
74
  const isUserScope = scope === 'user';
71
- const scopeLabel = isUserScope ? '用户' : '会话';
75
+ const scopeLabel = isUserScope ? '用户' : '全局';
72
76
 
73
77
  if (action === 'remember') {
74
78
  // 记住信息
@@ -90,7 +94,7 @@ const plugin: ToolPlugin = {
90
94
  if (isUserScope) {
91
95
  mm.rememberUser(context.userId, key, value);
92
96
  } else {
93
- mm.remember(context.groupId, key, value);
97
+ mm.remember(key, value);
94
98
  }
95
99
  return {
96
100
  success: true,
@@ -113,15 +117,15 @@ const plugin: ToolPlugin = {
113
117
  if (action === 'recall') {
114
118
  // 回忆信息
115
119
  try {
116
- // 默认同时查询用户级别和会话级别记忆
120
+ // 默认同时查询用户级别和全局级别记忆
117
121
  const userResult = mm.recallUser(context.userId, key);
118
- const groupResult = mm.recall(context.groupId, key);
119
-
122
+ const globalResult = mm.recall(key);
123
+
120
124
  if (key) {
121
- // 回忆特定键 - 优先返回用户级别,其次会话级别
122
- const result = userResult || groupResult;
123
- const foundScope = userResult ? '用户' : (groupResult ? '会话' : null);
124
-
125
+ // 回忆特定键 - 优先返回用户级别,其次全局级别
126
+ const result = userResult || globalResult;
127
+ const foundScope = userResult ? '用户' : (globalResult ? '全局' : null);
128
+
125
129
  if (result) {
126
130
  return {
127
131
  success: true,
@@ -144,15 +148,15 @@ const plugin: ToolPlugin = {
144
148
  };
145
149
  }
146
150
  } else {
147
- // 回忆所有 - 合并用户级别和会话级别
151
+ // 回忆所有 - 合并用户级别和全局级别
148
152
  const memories: string[] = [];
149
153
  if (userResult) {
150
154
  memories.push(`【用户记忆】\n${userResult}`);
151
155
  }
152
- if (groupResult) {
153
- memories.push(`【会话记忆】\n${groupResult}`);
156
+ if (globalResult) {
157
+ memories.push(`【全局记忆】\n${globalResult}`);
154
158
  }
155
-
159
+
156
160
  if (memories.length > 0) {
157
161
  return {
158
162
  success: true,
@@ -180,9 +184,38 @@ const plugin: ToolPlugin = {
180
184
  }
181
185
  }
182
186
 
187
+ if (action === 'log') {
188
+ // 追加每日日志
189
+ if (!value || typeof value !== 'string') {
190
+ return {
191
+ success: false,
192
+ error: 'log 操作需要提供 value(日志内容)'
193
+ };
194
+ }
195
+
196
+ try {
197
+ const log = mm.appendDailyLog(value);
198
+ return {
199
+ success: true,
200
+ data: {
201
+ action: 'logged',
202
+ date: log.date,
203
+ time: log.time,
204
+ content: value,
205
+ message: `已记录到 ${log.date} 日志`
206
+ }
207
+ };
208
+ } catch (error) {
209
+ return {
210
+ success: false,
211
+ error: `写入日志失败: ${error instanceof Error ? error.message : String(error)}`
212
+ };
213
+ }
214
+ }
215
+
183
216
  return {
184
217
  success: false,
185
- error: 'action 必须是 remember 或 recall'
218
+ error: 'action 必须是 remember、recalllog'
186
219
  };
187
220
  }
188
221
  };
@@ -1,161 +0,0 @@
1
- /**
2
- * FlashClaw 插件 - 取消定时任务
3
- * 取消或暂停定时任务
4
- */
5
-
6
- import { ToolPlugin, ToolContext, ToolResult } from '../../src/plugins/types.js';
7
- import { getTaskById, updateTask, deleteTask } from '../../src/db.js';
8
- import { MAIN_GROUP_FOLDER } from '../../src/config.js';
9
-
10
- /**
11
- * 取消任务参数
12
- */
13
- interface CancelTaskParams {
14
- /** 要取消的任务 ID */
15
- taskId: string;
16
- /** 操作类型:cancel(取消并删除)、pause(暂停)、resume(恢复) */
17
- action?: 'cancel' | 'pause' | 'resume';
18
- }
19
-
20
- const plugin: ToolPlugin = {
21
- name: 'cancel_task',
22
- version: '1.0.0',
23
- description: '取消、暂停或恢复定时任务',
24
-
25
- schema: {
26
- name: 'cancel_task',
27
- description: '管理定时任务的状态。可以取消(永久删除)、暂停或恢复任务。',
28
- input_schema: {
29
- type: 'object',
30
- properties: {
31
- taskId: {
32
- type: 'string',
33
- description: '任务 ID,可以通过 list_tasks 工具获取'
34
- },
35
- action: {
36
- type: 'string',
37
- enum: ['cancel', 'pause', 'resume'],
38
- description: '操作类型。cancel 永久删除任务,pause 暂停任务,resume 恢复已暂停的任务。默认为 cancel'
39
- }
40
- },
41
- required: ['taskId']
42
- }
43
- },
44
-
45
- async execute(params: unknown, context: ToolContext): Promise<ToolResult> {
46
- const { taskId, action = 'cancel' } = params as CancelTaskParams;
47
-
48
- // 参数验证
49
- if (!taskId || typeof taskId !== 'string') {
50
- return {
51
- success: false,
52
- error: '任务 ID 不能为空'
53
- };
54
- }
55
-
56
- if (!['cancel', 'pause', 'resume'].includes(action)) {
57
- return {
58
- success: false,
59
- error: '操作类型必须是 cancel、pause 或 resume'
60
- };
61
- }
62
-
63
- // 查找任务
64
- const task = getTaskById(taskId);
65
-
66
- if (!task) {
67
- return {
68
- success: false,
69
- error: `任务不存在: ${taskId}`
70
- };
71
- }
72
-
73
- // 权限检查:只能操作自己群组的任务,或者 main 群组可以操作所有任务
74
- const isMainGroup = context.groupId === MAIN_GROUP_FOLDER;
75
- const isOwnTask = task.group_folder === context.groupId;
76
-
77
- if (!isMainGroup && !isOwnTask) {
78
- return {
79
- success: false,
80
- error: '无权操作其他群组的任务'
81
- };
82
- }
83
-
84
- try {
85
- switch (action) {
86
- case 'cancel': {
87
- // 永久删除任务
88
- deleteTask(taskId);
89
- return {
90
- success: true,
91
- data: {
92
- taskId,
93
- action: 'cancelled',
94
- message: '任务已永久删除'
95
- }
96
- };
97
- }
98
-
99
- case 'pause': {
100
- // 暂停任务
101
- if (task.status === 'paused') {
102
- return {
103
- success: false,
104
- error: '任务已经是暂停状态'
105
- };
106
- }
107
- if (task.status === 'completed') {
108
- return {
109
- success: false,
110
- error: '已完成的任务无法暂停'
111
- };
112
- }
113
-
114
- updateTask(taskId, { status: 'paused' });
115
- return {
116
- success: true,
117
- data: {
118
- taskId,
119
- action: 'paused',
120
- message: '任务已暂停,可以使用 resume 操作恢复'
121
- }
122
- };
123
- }
124
-
125
- case 'resume': {
126
- // 恢复任务
127
- if (task.status !== 'paused') {
128
- return {
129
- success: false,
130
- error: '只能恢复已暂停的任务'
131
- };
132
- }
133
-
134
- updateTask(taskId, { status: 'active' });
135
- return {
136
- success: true,
137
- data: {
138
- taskId,
139
- action: 'resumed',
140
- message: '任务已恢复,将在下一个调度时间执行',
141
- nextRun: task.next_run
142
- }
143
- };
144
- }
145
-
146
- default:
147
- return {
148
- success: false,
149
- error: `未知操作: ${action}`
150
- };
151
- }
152
- } catch (error) {
153
- return {
154
- success: false,
155
- error: `操作失败: ${error instanceof Error ? error.message : String(error)}`
156
- };
157
- }
158
- }
159
- };
160
-
161
- export default plugin;
@@ -1,9 +0,0 @@
1
- {
2
- "name": "cancel-task",
3
- "version": "1.0.0",
4
- "type": "tool",
5
- "description": "取消定时任务",
6
- "main": "index.ts"
7
- }
8
-
9
-
@@ -1,51 +0,0 @@
1
- /**
2
- * CLI 渠道插件 - 终端交互渠道
3
- *
4
- * 作为 FlashClaw 的内置终端渠道,
5
- * 提供 CLI 命令客户端连接到服务
6
- *
7
- * 使用方式:
8
- * 1. 启动服务: flashclaw start
9
- * 2. 连接 CLI: flashclaw cli
10
- */
11
-
12
- import type { ChannelPlugin, MessageHandler, PluginConfig, SendMessageResult } from '../../src/plugins/types.js';
13
- import { createLogger } from '../../src/logger.js';
14
-
15
- const logger = createLogger('CLI-Channel');
16
-
17
- const plugin: ChannelPlugin & {
18
- group: string;
19
- } = {
20
- name: 'cli-channel',
21
- version: '1.0.0',
22
- group: 'cli-default',
23
-
24
- async init(_config: PluginConfig): Promise<void> {
25
- // CLI 渠道不需要特殊配置
26
- },
27
-
28
- onMessage(_handler: MessageHandler): void {
29
- // CLI 是客户端模式,由 flashclaw cli 命令连接
30
- // 消息通过 HTTP API 传输
31
- },
32
-
33
- async start(): Promise<void> {
34
- // CLI 渠道是客户端模式,不绑定终端
35
- // 用户通过 flashclaw cli 命令连接
36
- // 消息通过 web-ui 的 /api/chat 接口传输
37
- logger.info('CLI 渠道已就绪,使用 flashclaw cli 连接服务');
38
- },
39
-
40
- async stop(): Promise<void> {
41
- // 清理资源
42
- },
43
-
44
- async sendMessage(_chatId: string, content: string): Promise<SendMessageResult> {
45
- // CLI 渠道的消息由 flashclaw cli 命令处理
46
- // 这里不需要实现(消息通过 API 传输)
47
- return { success: true };
48
- }
49
- };
50
-
51
- export default plugin;
@@ -1,8 +0,0 @@
1
- {
2
- "name": "cli-channel",
3
- "version": "1.0.0",
4
- "type": "channel",
5
- "description": "终端交互渠道 - CLI REPL 模式",
6
- "main": "index.ts",
7
- "dependencies": []
8
- }