@lobehub/lobehub 2.0.0-next.327 → 2.0.0-next.328
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/en-US/chat.json +6 -1
- package/locales/zh-CN/chat.json +5 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +24 -0
- package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +210 -0
- package/packages/agent-runtime/src/types/instruction.ts +46 -2
- package/packages/builtin-tool-gtd/src/const.ts +1 -0
- package/packages/builtin-tool-gtd/src/executor/index.ts +38 -21
- package/packages/builtin-tool-gtd/src/manifest.ts +15 -0
- package/packages/builtin-tool-gtd/src/systemRole.ts +33 -1
- package/packages/builtin-tool-gtd/src/types.ts +55 -33
- package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +1 -0
- package/packages/builtin-tool-local-system/src/client/Inspector/RunCommand/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +5 -1
- package/packages/builtin-tool-notebook/src/systemRole.ts +27 -7
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +13 -1
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +40 -0
- package/packages/database/src/models/__tests__/messages/message.thread-query.test.ts +134 -1
- package/packages/database/src/models/message.ts +8 -1
- package/packages/database/src/models/thread.ts +1 -1
- package/packages/types/src/message/ui/chat.ts +2 -0
- package/packages/types/src/topic/thread.ts +20 -0
- package/src/components/StreamingMarkdown/index.tsx +10 -43
- package/src/features/Conversation/Messages/AssistantGroup/components/MessageContent.tsx +0 -2
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +108 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InitializingState.tsx +66 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +63 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +123 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +106 -0
- package/src/features/Conversation/Messages/Task/TaskDetailPanel/index.tsx +1 -0
- package/src/features/Conversation/Messages/Task/index.tsx +11 -6
- package/src/features/Conversation/Messages/Tasks/TaskItem/TaskTitle.tsx +3 -2
- package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +0 -4
- package/src/features/Conversation/Messages/Tasks/shared/utils.ts +22 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +1 -1
- package/src/features/Conversation/components/Thinking/index.tsx +9 -30
- package/src/features/Conversation/store/slices/data/action.ts +2 -3
- package/src/features/NavPanel/components/BackButton.tsx +10 -13
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +4 -0
- package/src/hooks/useAutoScroll.ts +117 -0
- package/src/locales/default/chat.ts +6 -1
- package/src/server/routers/lambda/aiAgent.ts +239 -1
- package/src/server/routers/lambda/thread.ts +2 -0
- package/src/server/services/message/__tests__/index.test.ts +37 -0
- package/src/server/services/message/index.ts +6 -1
- package/src/services/aiAgent.ts +51 -0
- package/src/store/chat/agents/createAgentExecutors.ts +714 -12
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -1
- package/src/store/chat/slices/message/actions/query.ts +33 -1
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +10 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -0
- package/src/store/chat/slices/operation/types.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.328](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.327...v2.0.0-next.328)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-20**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Support client tasks mode.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Support client tasks mode, closes [#11666](https://github.com/lobehub/lobe-chat/issues/11666) ([98cf57b](https://github.com/lobehub/lobe-chat/commit/98cf57b))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.327](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.326...v2.0.0-next.327)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-20**</sup>
|
package/changelog/v1.json
CHANGED
package/locales/en-US/chat.json
CHANGED
|
@@ -204,6 +204,7 @@
|
|
|
204
204
|
"noSelectedAgents": "No members selected yet",
|
|
205
205
|
"openInNewWindow": "Open in New Window",
|
|
206
206
|
"operation.execAgentRuntime": "Preparing response",
|
|
207
|
+
"operation.execClientTask": "Executing task",
|
|
207
208
|
"operation.sendMessage": "Sending message",
|
|
208
209
|
"owner": "Group owner",
|
|
209
210
|
"pageCopilot.title": "Page Agent",
|
|
@@ -322,13 +323,17 @@
|
|
|
322
323
|
"tab.profile": "Agent Profile",
|
|
323
324
|
"tab.search": "Search",
|
|
324
325
|
"task.activity.calling": "Calling Skill...",
|
|
326
|
+
"task.activity.clientExecuting": "Executing locally...",
|
|
325
327
|
"task.activity.generating": "Generating response...",
|
|
326
328
|
"task.activity.gotResult": "Tool result received",
|
|
327
329
|
"task.activity.toolCalling": "Calling {{toolName}}...",
|
|
328
330
|
"task.activity.toolResult": "{{toolName}} result received",
|
|
329
331
|
"task.batchTasks": "{{count}} Batch Subtasks",
|
|
332
|
+
"task.instruction": "Task Instruction",
|
|
333
|
+
"task.intermediateSteps": "{{count}} intermediate steps",
|
|
334
|
+
"task.metrics.duration": "(took {{duration}})",
|
|
330
335
|
"task.metrics.stepsShort": "steps",
|
|
331
|
-
"task.metrics.toolCallsShort": "
|
|
336
|
+
"task.metrics.toolCallsShort": "skill uses",
|
|
332
337
|
"task.status.cancelled": "Task Cancelled",
|
|
333
338
|
"task.status.failed": "Task Failed",
|
|
334
339
|
"task.status.initializing": "Initializing task...",
|
package/locales/zh-CN/chat.json
CHANGED
|
@@ -204,6 +204,7 @@
|
|
|
204
204
|
"noSelectedAgents": "还未选择成员",
|
|
205
205
|
"openInNewWindow": "在新窗口打开",
|
|
206
206
|
"operation.execAgentRuntime": "准备响应中",
|
|
207
|
+
"operation.execClientTask": "执行任务中",
|
|
207
208
|
"operation.sendMessage": "消息发送中",
|
|
208
209
|
"owner": "群主",
|
|
209
210
|
"pageCopilot.title": "文稿助理",
|
|
@@ -322,11 +323,15 @@
|
|
|
322
323
|
"tab.profile": "助理档案",
|
|
323
324
|
"tab.search": "搜索",
|
|
324
325
|
"task.activity.calling": "正在调用技能…",
|
|
326
|
+
"task.activity.clientExecuting": "本地执行中…",
|
|
325
327
|
"task.activity.generating": "正在生成回复…",
|
|
326
328
|
"task.activity.gotResult": "已获取技能结果",
|
|
327
329
|
"task.activity.toolCalling": "正在调用 {{toolName}}…",
|
|
328
330
|
"task.activity.toolResult": "已获取 {{toolName}} 结果",
|
|
329
331
|
"task.batchTasks": "{{count}} 个批量子任务",
|
|
332
|
+
"task.instruction": "任务说明",
|
|
333
|
+
"task.intermediateSteps": "{{count}} 个中间步骤",
|
|
334
|
+
"task.metrics.duration": "(用时 {{duration}})",
|
|
330
335
|
"task.metrics.stepsShort": "步",
|
|
331
336
|
"task.metrics.toolCallsShort": "次技能调用",
|
|
332
337
|
"task.status.cancelled": "任务已取消",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.328",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -359,6 +359,30 @@ export class GeneralChatAgent implements Agent {
|
|
|
359
359
|
type: 'exec_tasks',
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
|
+
|
|
363
|
+
// GTD client-side async task (single, desktop only)
|
|
364
|
+
if (stateType === 'execClientTask') {
|
|
365
|
+
const { parentMessageId: execParentId, task } = data.state as {
|
|
366
|
+
parentMessageId: string;
|
|
367
|
+
task: any;
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
payload: { parentMessageId: execParentId, task },
|
|
371
|
+
type: 'exec_client_task',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// GTD client-side async tasks (multiple, desktop only)
|
|
376
|
+
if (stateType === 'execClientTasks') {
|
|
377
|
+
const { parentMessageId: execParentId, tasks } = data.state as {
|
|
378
|
+
parentMessageId: string;
|
|
379
|
+
tasks: any[];
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
payload: { parentMessageId: execParentId, tasks },
|
|
383
|
+
type: 'exec_client_tasks',
|
|
384
|
+
};
|
|
385
|
+
}
|
|
362
386
|
}
|
|
363
387
|
|
|
364
388
|
// Check if there are still pending tool messages waiting for approval
|
|
@@ -368,6 +368,216 @@ describe('GeneralChatAgent', () => {
|
|
|
368
368
|
});
|
|
369
369
|
|
|
370
370
|
describe('tool_result phase', () => {
|
|
371
|
+
describe('GTD async tasks', () => {
|
|
372
|
+
it('should return exec_task for single async task (execTask)', async () => {
|
|
373
|
+
const agent = new GeneralChatAgent({
|
|
374
|
+
agentConfig: { maxSteps: 100 },
|
|
375
|
+
operationId: 'test-session',
|
|
376
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const state = createMockState();
|
|
380
|
+
const context = createMockContext('tool_result', {
|
|
381
|
+
parentMessageId: 'tool-msg-1',
|
|
382
|
+
stop: true,
|
|
383
|
+
data: {
|
|
384
|
+
state: {
|
|
385
|
+
type: 'execTask',
|
|
386
|
+
parentMessageId: 'exec-parent-msg',
|
|
387
|
+
task: { instruction: 'Do something async', timeout: 30000 },
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = await agent.runner(context, state);
|
|
393
|
+
|
|
394
|
+
expect(result).toEqual({
|
|
395
|
+
type: 'exec_task',
|
|
396
|
+
payload: {
|
|
397
|
+
parentMessageId: 'exec-parent-msg',
|
|
398
|
+
task: { instruction: 'Do something async', timeout: 30000 },
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should return exec_tasks for multiple async tasks (execTasks)', async () => {
|
|
404
|
+
const agent = new GeneralChatAgent({
|
|
405
|
+
agentConfig: { maxSteps: 100 },
|
|
406
|
+
operationId: 'test-session',
|
|
407
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const state = createMockState();
|
|
411
|
+
const tasks = [
|
|
412
|
+
{ instruction: 'Task 1', timeout: 30000 },
|
|
413
|
+
{ instruction: 'Task 2', timeout: 30000 },
|
|
414
|
+
];
|
|
415
|
+
const context = createMockContext('tool_result', {
|
|
416
|
+
parentMessageId: 'tool-msg-1',
|
|
417
|
+
stop: true,
|
|
418
|
+
data: {
|
|
419
|
+
state: {
|
|
420
|
+
type: 'execTasks',
|
|
421
|
+
parentMessageId: 'exec-parent-msg',
|
|
422
|
+
tasks,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const result = await agent.runner(context, state);
|
|
428
|
+
|
|
429
|
+
expect(result).toEqual({
|
|
430
|
+
type: 'exec_tasks',
|
|
431
|
+
payload: {
|
|
432
|
+
parentMessageId: 'exec-parent-msg',
|
|
433
|
+
tasks,
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('should return exec_client_task for single client-side async task (execClientTask)', async () => {
|
|
439
|
+
const agent = new GeneralChatAgent({
|
|
440
|
+
agentConfig: { maxSteps: 100 },
|
|
441
|
+
operationId: 'test-session',
|
|
442
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const state = createMockState();
|
|
446
|
+
const context = createMockContext('tool_result', {
|
|
447
|
+
parentMessageId: 'tool-msg-1',
|
|
448
|
+
stop: true,
|
|
449
|
+
data: {
|
|
450
|
+
state: {
|
|
451
|
+
type: 'execClientTask',
|
|
452
|
+
parentMessageId: 'exec-parent-msg',
|
|
453
|
+
task: { type: 'localFile', path: '/path/to/file' },
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const result = await agent.runner(context, state);
|
|
459
|
+
|
|
460
|
+
expect(result).toEqual({
|
|
461
|
+
type: 'exec_client_task',
|
|
462
|
+
payload: {
|
|
463
|
+
parentMessageId: 'exec-parent-msg',
|
|
464
|
+
task: { type: 'localFile', path: '/path/to/file' },
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should return exec_client_tasks for multiple client-side async tasks (execClientTasks)', async () => {
|
|
470
|
+
const agent = new GeneralChatAgent({
|
|
471
|
+
agentConfig: { maxSteps: 100 },
|
|
472
|
+
operationId: 'test-session',
|
|
473
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const state = createMockState();
|
|
477
|
+
const tasks = [
|
|
478
|
+
{ type: 'localFile', path: '/path/to/file1' },
|
|
479
|
+
{ type: 'localFile', path: '/path/to/file2' },
|
|
480
|
+
];
|
|
481
|
+
const context = createMockContext('tool_result', {
|
|
482
|
+
parentMessageId: 'tool-msg-1',
|
|
483
|
+
stop: true,
|
|
484
|
+
data: {
|
|
485
|
+
state: {
|
|
486
|
+
type: 'execClientTasks',
|
|
487
|
+
parentMessageId: 'exec-parent-msg',
|
|
488
|
+
tasks,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const result = await agent.runner(context, state);
|
|
494
|
+
|
|
495
|
+
expect(result).toEqual({
|
|
496
|
+
type: 'exec_client_tasks',
|
|
497
|
+
payload: {
|
|
498
|
+
parentMessageId: 'exec-parent-msg',
|
|
499
|
+
tasks,
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should not trigger exec_task when stop is false', async () => {
|
|
505
|
+
const agent = new GeneralChatAgent({
|
|
506
|
+
agentConfig: { maxSteps: 100 },
|
|
507
|
+
operationId: 'test-session',
|
|
508
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const state = createMockState({
|
|
512
|
+
messages: [
|
|
513
|
+
{ role: 'user', content: 'Hello' },
|
|
514
|
+
{ role: 'assistant', content: '' },
|
|
515
|
+
{ role: 'tool', content: 'Result', tool_call_id: 'call-1' },
|
|
516
|
+
] as any,
|
|
517
|
+
});
|
|
518
|
+
const context = createMockContext('tool_result', {
|
|
519
|
+
parentMessageId: 'tool-msg-1',
|
|
520
|
+
stop: false, // stop is false, should not trigger exec_task
|
|
521
|
+
data: {
|
|
522
|
+
state: {
|
|
523
|
+
type: 'execTask',
|
|
524
|
+
parentMessageId: 'exec-parent-msg',
|
|
525
|
+
task: { instruction: 'Do something async' },
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const result = await agent.runner(context, state);
|
|
531
|
+
|
|
532
|
+
// Should return call_llm instead of exec_task
|
|
533
|
+
expect(result).toEqual({
|
|
534
|
+
type: 'call_llm',
|
|
535
|
+
payload: {
|
|
536
|
+
messages: state.messages,
|
|
537
|
+
model: 'gpt-4o-mini',
|
|
538
|
+
parentMessageId: 'tool-msg-1',
|
|
539
|
+
provider: 'openai',
|
|
540
|
+
tools: undefined,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should not trigger exec_task when data.state is undefined', async () => {
|
|
546
|
+
const agent = new GeneralChatAgent({
|
|
547
|
+
agentConfig: { maxSteps: 100 },
|
|
548
|
+
operationId: 'test-session',
|
|
549
|
+
modelRuntimeConfig: mockModelRuntimeConfig,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const state = createMockState({
|
|
553
|
+
messages: [
|
|
554
|
+
{ role: 'user', content: 'Hello' },
|
|
555
|
+
{ role: 'assistant', content: '' },
|
|
556
|
+
{ role: 'tool', content: 'Result', tool_call_id: 'call-1' },
|
|
557
|
+
] as any,
|
|
558
|
+
});
|
|
559
|
+
const context = createMockContext('tool_result', {
|
|
560
|
+
parentMessageId: 'tool-msg-1',
|
|
561
|
+
stop: true,
|
|
562
|
+
data: {}, // No state property
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const result = await agent.runner(context, state);
|
|
566
|
+
|
|
567
|
+
// Should return call_llm instead of exec_task
|
|
568
|
+
expect(result).toEqual({
|
|
569
|
+
type: 'call_llm',
|
|
570
|
+
payload: {
|
|
571
|
+
messages: state.messages,
|
|
572
|
+
model: 'gpt-4o-mini',
|
|
573
|
+
parentMessageId: 'tool-msg-1',
|
|
574
|
+
provider: 'openai',
|
|
575
|
+
tools: undefined,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
371
581
|
it('should return call_llm when no pending tools', async () => {
|
|
372
582
|
const agent = new GeneralChatAgent({
|
|
373
583
|
agentConfig: { maxSteps: 100 },
|
|
@@ -236,12 +236,26 @@ export interface ExecTaskItem {
|
|
|
236
236
|
inheritMessages?: boolean;
|
|
237
237
|
/** Detailed instruction/prompt for the task execution */
|
|
238
238
|
instruction: string;
|
|
239
|
+
/**
|
|
240
|
+
* Whether to execute the task on the client side (desktop only).
|
|
241
|
+
* When true and running on desktop, the task will be executed locally
|
|
242
|
+
* with access to local tools (file system, shell commands, etc.).
|
|
243
|
+
*
|
|
244
|
+
* IMPORTANT: This MUST be set to true when the task requires:
|
|
245
|
+
* - Reading/writing local files via `local-system` tool
|
|
246
|
+
* - Executing shell commands
|
|
247
|
+
* - Any other desktop-only local tool operations
|
|
248
|
+
*
|
|
249
|
+
* If not specified or false, the task runs on the server (default behavior).
|
|
250
|
+
* On non-desktop platforms (web), this flag is ignored and tasks always run on server.
|
|
251
|
+
*/
|
|
252
|
+
runInClient?: boolean;
|
|
239
253
|
/** Timeout in milliseconds (optional, default 30 minutes) */
|
|
240
254
|
timeout?: number;
|
|
241
255
|
}
|
|
242
256
|
|
|
243
257
|
/**
|
|
244
|
-
* Instruction to execute a single async task
|
|
258
|
+
* Instruction to execute a single async task (server-side)
|
|
245
259
|
*/
|
|
246
260
|
export interface AgentInstructionExecTask {
|
|
247
261
|
payload: {
|
|
@@ -254,7 +268,7 @@ export interface AgentInstructionExecTask {
|
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
/**
|
|
257
|
-
* Instruction to execute multiple async tasks in parallel
|
|
271
|
+
* Instruction to execute multiple async tasks in parallel (server-side)
|
|
258
272
|
*/
|
|
259
273
|
export interface AgentInstructionExecTasks {
|
|
260
274
|
payload: {
|
|
@@ -266,6 +280,34 @@ export interface AgentInstructionExecTasks {
|
|
|
266
280
|
type: 'exec_tasks';
|
|
267
281
|
}
|
|
268
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Instruction to execute a single async task on the client (desktop only)
|
|
285
|
+
* Used when task requires local tools like file system or shell commands
|
|
286
|
+
*/
|
|
287
|
+
export interface AgentInstructionExecClientTask {
|
|
288
|
+
payload: {
|
|
289
|
+
/** Parent message ID (tool message that triggered the task) */
|
|
290
|
+
parentMessageId: string;
|
|
291
|
+
/** Task to execute */
|
|
292
|
+
task: ExecTaskItem;
|
|
293
|
+
};
|
|
294
|
+
type: 'exec_client_task';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Instruction to execute multiple async tasks on the client in parallel (desktop only)
|
|
299
|
+
* Used when tasks require local tools like file system or shell commands
|
|
300
|
+
*/
|
|
301
|
+
export interface AgentInstructionExecClientTasks {
|
|
302
|
+
payload: {
|
|
303
|
+
/** Parent message ID (tool message that triggered the tasks) */
|
|
304
|
+
parentMessageId: string;
|
|
305
|
+
/** Array of tasks to execute */
|
|
306
|
+
tasks: ExecTaskItem[];
|
|
307
|
+
};
|
|
308
|
+
type: 'exec_client_tasks';
|
|
309
|
+
}
|
|
310
|
+
|
|
269
311
|
/**
|
|
270
312
|
* Payload for task_result phase (single task)
|
|
271
313
|
*/
|
|
@@ -318,6 +360,8 @@ export type AgentInstruction =
|
|
|
318
360
|
| AgentInstructionCallToolsBatch
|
|
319
361
|
| AgentInstructionExecTask
|
|
320
362
|
| AgentInstructionExecTasks
|
|
363
|
+
| AgentInstructionExecClientTask
|
|
364
|
+
| AgentInstructionExecClientTasks
|
|
321
365
|
| AgentInstructionRequestHumanPrompt
|
|
322
366
|
| AgentInstructionRequestHumanSelect
|
|
323
367
|
| AgentInstructionRequestHumanApprove
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
|
@@ -407,18 +407,20 @@ class GTDExecutor extends BaseExecutor<typeof GTDApiNameEnum> {
|
|
|
407
407
|
* Execute a single async task
|
|
408
408
|
*
|
|
409
409
|
* This method triggers async task execution by returning a special state.
|
|
410
|
-
* The AgentRuntime's executor will recognize this state and trigger the
|
|
410
|
+
* The AgentRuntime's executor will recognize this state and trigger the appropriate instruction.
|
|
411
411
|
*
|
|
412
412
|
* Flow:
|
|
413
|
-
* 1. GTD tool returns stop: true with state.type = 'execTask'
|
|
414
|
-
* 2. AgentRuntime executor recognizes the state and triggers exec_task instruction
|
|
415
|
-
* 3.
|
|
413
|
+
* 1. GTD tool returns stop: true with state.type = 'execTask' or 'execClientTask'
|
|
414
|
+
* 2. AgentRuntime executor recognizes the state and triggers exec_task or exec_client_task instruction
|
|
415
|
+
* 3. The executor creates task message and handles execution
|
|
416
|
+
*
|
|
417
|
+
* @param params.runInClient - If true, returns 'execClientTask' state for client-side execution
|
|
416
418
|
*/
|
|
417
419
|
execTask = async (
|
|
418
420
|
params: ExecTaskParams,
|
|
419
421
|
ctx: BuiltinToolContext,
|
|
420
422
|
): Promise<BuiltinToolResult> => {
|
|
421
|
-
const { description, instruction, inheritMessages, timeout } = params;
|
|
423
|
+
const { description, instruction, inheritMessages, timeout, runInClient } = params;
|
|
422
424
|
|
|
423
425
|
if (!description || !instruction) {
|
|
424
426
|
return {
|
|
@@ -427,19 +429,25 @@ class GTDExecutor extends BaseExecutor<typeof GTDApiNameEnum> {
|
|
|
427
429
|
};
|
|
428
430
|
}
|
|
429
431
|
|
|
432
|
+
const task = {
|
|
433
|
+
description,
|
|
434
|
+
inheritMessages,
|
|
435
|
+
instruction,
|
|
436
|
+
runInClient,
|
|
437
|
+
timeout,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Determine state type based on runInClient
|
|
441
|
+
// If runInClient is true, return 'execClientTask' to trigger client-side executor
|
|
442
|
+
const stateType = runInClient ? 'execClientTask' : 'execTask';
|
|
443
|
+
|
|
430
444
|
// Return stop: true with special state that AgentRuntime will recognize
|
|
431
|
-
// The exec_task executor will be triggered by the runtime when it sees this state
|
|
432
445
|
return {
|
|
433
|
-
content: `🚀 Triggered async task for execution:\n- ${description}`,
|
|
446
|
+
content: `🚀 Triggered async task for ${runInClient ? 'client-side' : ''} execution:\n- ${description}`,
|
|
434
447
|
state: {
|
|
435
448
|
parentMessageId: ctx.messageId,
|
|
436
|
-
task
|
|
437
|
-
|
|
438
|
-
inheritMessages,
|
|
439
|
-
instruction,
|
|
440
|
-
timeout,
|
|
441
|
-
},
|
|
442
|
-
type: 'execTask',
|
|
449
|
+
task,
|
|
450
|
+
type: stateType,
|
|
443
451
|
},
|
|
444
452
|
stop: true,
|
|
445
453
|
success: true,
|
|
@@ -450,12 +458,15 @@ class GTDExecutor extends BaseExecutor<typeof GTDApiNameEnum> {
|
|
|
450
458
|
* Execute one or more async tasks
|
|
451
459
|
*
|
|
452
460
|
* This method triggers async task execution by returning a special state.
|
|
453
|
-
* The AgentRuntime's executor will recognize this state and trigger the
|
|
461
|
+
* The AgentRuntime's executor will recognize this state and trigger the appropriate instruction.
|
|
454
462
|
*
|
|
455
463
|
* Flow:
|
|
456
|
-
* 1. GTD tool returns stop: true with state.type = 'execTasks'
|
|
457
|
-
* 2. AgentRuntime executor recognizes the state and triggers exec_tasks instruction
|
|
458
|
-
* 3.
|
|
464
|
+
* 1. GTD tool returns stop: true with state.type = 'execTasks' or 'execClientTasks'
|
|
465
|
+
* 2. AgentRuntime executor recognizes the state and triggers exec_tasks or exec_client_tasks instruction
|
|
466
|
+
* 3. The executor creates task messages and handles execution
|
|
467
|
+
*
|
|
468
|
+
* Note: If any task has runInClient=true, all tasks will be routed to 'execClientTasks'.
|
|
469
|
+
* This is because client-side execution is the "special" case requiring local tool access.
|
|
459
470
|
*/
|
|
460
471
|
execTasks = async (
|
|
461
472
|
params: ExecTasksParams,
|
|
@@ -473,14 +484,20 @@ class GTDExecutor extends BaseExecutor<typeof GTDApiNameEnum> {
|
|
|
473
484
|
const taskCount = tasks.length;
|
|
474
485
|
const taskList = tasks.map((t, i) => `${i + 1}. ${t.description}`).join('\n');
|
|
475
486
|
|
|
487
|
+
// Check if any task requires client-side execution
|
|
488
|
+
const hasClientTasks = tasks.some((t) => t.runInClient);
|
|
489
|
+
|
|
490
|
+
// Determine state type: if any task needs client-side, route all to client executor
|
|
491
|
+
const stateType = hasClientTasks ? 'execClientTasks' : 'execTasks';
|
|
492
|
+
const executionMode = hasClientTasks ? 'client-side' : '';
|
|
493
|
+
|
|
476
494
|
// Return stop: true with special state that AgentRuntime will recognize
|
|
477
|
-
// The exec_tasks executor will be triggered by the runtime when it sees this state
|
|
478
495
|
return {
|
|
479
|
-
content: `🚀 Triggered ${taskCount} async task${taskCount > 1 ? 's' : ''} for execution:\n${taskList}`,
|
|
496
|
+
content: `🚀 Triggered ${taskCount} async task${taskCount > 1 ? 's' : ''} for ${executionMode} execution:\n${taskList}`,
|
|
480
497
|
state: {
|
|
481
498
|
parentMessageId: ctx.messageId,
|
|
482
499
|
tasks,
|
|
483
|
-
type:
|
|
500
|
+
type: stateType,
|
|
484
501
|
},
|
|
485
502
|
stop: true,
|
|
486
503
|
success: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BuiltinToolManifest } from '@lobechat/types';
|
|
2
2
|
|
|
3
|
+
import { isDesktop } from './const';
|
|
3
4
|
import { systemPrompt } from './systemRole';
|
|
4
5
|
import { GTDApiName } from './types';
|
|
5
6
|
|
|
@@ -171,6 +172,13 @@ export const GTDManifest: BuiltinToolManifest = {
|
|
|
171
172
|
'Whether to inherit context messages from the parent conversation. Default is false.',
|
|
172
173
|
type: 'boolean',
|
|
173
174
|
},
|
|
175
|
+
...(isDesktop && {
|
|
176
|
+
runInClient: {
|
|
177
|
+
description:
|
|
178
|
+
'Whether to run on the desktop client (for local file/shell access). MUST be true when task requires local-system tools. Default is false (server execution).',
|
|
179
|
+
type: 'boolean',
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
174
182
|
timeout: {
|
|
175
183
|
description: 'Optional timeout in milliseconds. Default is 30 minutes.',
|
|
176
184
|
type: 'number',
|
|
@@ -203,6 +211,13 @@ export const GTDManifest: BuiltinToolManifest = {
|
|
|
203
211
|
'Whether to inherit context messages from the parent conversation. Default is false.',
|
|
204
212
|
type: 'boolean',
|
|
205
213
|
},
|
|
214
|
+
...(isDesktop && {
|
|
215
|
+
runInClient: {
|
|
216
|
+
description:
|
|
217
|
+
'Whether to run on the desktop client (for local file/shell access). MUST be true when task requires local-system tools. Default is false (server execution).',
|
|
218
|
+
type: 'boolean',
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
206
221
|
timeout: {
|
|
207
222
|
description: 'Optional timeout in milliseconds. Default is 30 minutes.',
|
|
208
223
|
type: 'number',
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
import { isDesktop } from './const';
|
|
2
|
+
|
|
3
|
+
const runInClientSection = `
|
|
4
|
+
<run_in_client>
|
|
5
|
+
**IMPORTANT: When to use \`runInClient: true\` for async tasks**
|
|
6
|
+
|
|
7
|
+
The \`runInClient\` parameter controls WHERE the async task executes:
|
|
8
|
+
- \`runInClient: false\` (default): Task runs on the **server** - suitable for web searches, API calls, general research
|
|
9
|
+
- \`runInClient: true\`: Task runs on the **desktop client** - required for local system access
|
|
10
|
+
|
|
11
|
+
**MUST set \`runInClient: true\` when the task involves:**
|
|
12
|
+
- Reading or writing local files (via \`local-system\` tool)
|
|
13
|
+
- Executing shell commands on the user's machine
|
|
14
|
+
- Accessing local directories or file system
|
|
15
|
+
- Any operation that requires desktop-only local tools
|
|
16
|
+
|
|
17
|
+
**Keep \`runInClient: false\` (or omit) when:**
|
|
18
|
+
- Task only needs web searches or API calls
|
|
19
|
+
- Task processes data that doesn't require local file access
|
|
20
|
+
- Task can be fully completed with server-side capabilities
|
|
21
|
+
|
|
22
|
+
**Note:** \`runInClient\` only has effect on the **desktop app**. On web platform, tasks always run on the server regardless of this setting.
|
|
23
|
+
|
|
24
|
+
**Examples:**
|
|
25
|
+
- "Research Python best practices" → \`runInClient: false\` (web search only)
|
|
26
|
+
- "Organize files in my Downloads folder" → \`runInClient: true\` (local file access required)
|
|
27
|
+
- "Read the project README and summarize it" → \`runInClient: true\` (local file read required)
|
|
28
|
+
- "Find trending tech news" → \`runInClient: false\` (web search only)
|
|
29
|
+
- "Create a new directory structure for my project" → \`runInClient: true\` (local shell/file required)
|
|
30
|
+
</run_in_client>
|
|
31
|
+
`;
|
|
32
|
+
|
|
1
33
|
export const systemPrompt = `You have GTD (Getting Things Done) tools to help manage plans, todos and tasks effectively. These tools support three levels of task management:
|
|
2
34
|
|
|
3
35
|
- **Plan**: A high-level strategic document describing goals, context, and overall direction. Plans do NOT contain actionable steps - they define the "what" and "why". **Plans should be stable once created** - they represent the overarching objective that rarely changes.
|
|
@@ -85,7 +117,7 @@ Use \`execTask\` for a single task, \`execTasks\` for multiple parallel tasks.
|
|
|
85
117
|
- User asks a factual question you know → Just answer directly
|
|
86
118
|
- User wants multiple independent analyses → execTasks (parallel execution)
|
|
87
119
|
</when_to_use>
|
|
88
|
-
|
|
120
|
+
${isDesktop ? runInClientSection : ''}
|
|
89
121
|
<best_practices>
|
|
90
122
|
- **Plan first, then todos**: Always start with a plan unless explicitly told otherwise
|
|
91
123
|
- **Separate concerns**: Plans describe goals; Todos list actions
|