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.
- package/README.md +115 -45
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/agent-runner.d.ts +2 -0
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +280 -36
- package/dist/agent-runner.js.map +1 -1
- package/dist/channel-manager.d.ts.map +1 -1
- package/dist/channel-manager.js +17 -9
- package/dist/channel-manager.js.map +1 -1
- package/dist/cli-ink.d.ts +11 -0
- package/dist/cli-ink.d.ts.map +1 -0
- package/dist/cli-ink.js +495 -0
- package/dist/cli-ink.js.map +1 -0
- package/dist/cli.js +18 -355
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +2 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +92 -4
- package/dist/commands.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/context-guard.d.ts +3 -3
- package/dist/core/context-guard.d.ts.map +1 -1
- package/dist/core/context-guard.js +32 -18
- package/dist/core/context-guard.js.map +1 -1
- package/dist/core/memory.d.ts +54 -31
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core/memory.js +233 -97
- package/dist/core/memory.js.map +1 -1
- package/dist/core/model-capabilities.d.ts.map +1 -1
- package/dist/core/model-capabilities.js +4 -2
- package/dist/core/model-capabilities.js.map +1 -1
- package/dist/core-api.d.ts +117 -0
- package/dist/core-api.d.ts.map +1 -0
- package/dist/core-api.js +218 -0
- package/dist/core-api.js.map +1 -0
- package/dist/health.js +1 -1
- package/dist/health.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -78
- package/dist/index.js.map +1 -1
- package/dist/message-queue.js +1 -1
- package/dist/message-queue.js.map +1 -1
- package/dist/paths.d.ts +4 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +7 -0
- package/dist/paths.js.map +1 -1
- package/dist/plugins/loader.js +4 -4
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/plugins/manager.js +34 -13
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/types.d.ts +3 -0
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js.map +1 -1
- package/dist/soul-manager.d.ts +42 -0
- package/dist/soul-manager.d.ts.map +1 -0
- package/dist/soul-manager.js +203 -0
- package/dist/soul-manager.js.map +1 -0
- package/dist/task-scheduler.d.ts.map +1 -1
- package/dist/task-scheduler.js +41 -24
- package/dist/task-scheduler.js.map +1 -1
- package/dist/tool-params.d.ts +13 -0
- package/dist/tool-params.d.ts.map +1 -0
- package/dist/tool-params.js +239 -0
- package/dist/tool-params.js.map +1 -0
- package/dist/types.d.ts +33 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugins/anthropic-provider/index.ts +188 -6
- package/plugins/memory/index.ts +68 -35
- package/plugins/cancel-task/index.ts +0 -161
- package/plugins/cancel-task/plugin.json +0 -9
- package/plugins/cli-channel/index.ts +0 -51
- package/plugins/cli-channel/plugin.json +0 -8
- package/plugins/list-tasks/index.ts +0 -150
- package/plugins/list-tasks/plugin.json +0 -9
- package/plugins/pause-task/index.ts +0 -95
- package/plugins/pause-task/plugin.json +0 -8
- package/plugins/register-group/index.ts +0 -147
- package/plugins/register-group/plugin.json +0 -7
- package/plugins/resume-task/index.ts +0 -92
- package/plugins/resume-task/plugin.json +0 -8
- package/plugins/schedule-task/index.ts +0 -248
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/plugins/memory/index.ts
CHANGED
|
@@ -10,39 +10,44 @@ import { getMemoryManager } from '../../src/core/memory.js';
|
|
|
10
10
|
* 记忆操作参数
|
|
11
11
|
*/
|
|
12
12
|
interface MemoryParams {
|
|
13
|
-
/** 操作类型:remember
|
|
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
|
|
20
|
-
scope?: 'user' | '
|
|
19
|
+
/** 作用域:user(用户级别)或 global(全局共享,默认) */
|
|
20
|
+
scope?: 'user' | 'global';
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const plugin: ToolPlugin = {
|
|
24
24
|
name: 'memory',
|
|
25
|
-
version: '1.
|
|
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
|
-
|
|
36
|
-
|
|
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', '
|
|
58
|
-
description: '作用域。user
|
|
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 = '
|
|
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(
|
|
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
|
|
119
|
-
|
|
122
|
+
const globalResult = mm.recall(key);
|
|
123
|
+
|
|
120
124
|
if (key) {
|
|
121
|
-
// 回忆特定键 -
|
|
122
|
-
const result = userResult ||
|
|
123
|
-
const foundScope = userResult ? '用户' : (
|
|
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 (
|
|
153
|
-
memories.push(
|
|
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 或
|
|
218
|
+
error: 'action 必须是 remember、recall 或 log'
|
|
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,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;
|