♻️ refactor(builtin-tool): absorb GTD tool (plan + todo) into lobe-agent

Delete `packages/builtin-tool-gtd/` and fold its full surface — plan, todo,
ExecutionRuntime, all client UI (Inspector / Render / Streaming /
Intervention / SortableTodoList) and the system role — into
`packages/builtin-tool-lobe-agent/`. Single `lobe-agent` identifier now
owns: plan + todo management, sub-agent dispatch, and visual media analysis.

Also restructures the lobe-agent package so the executor lives under
`./client/` alongside the UI it ships with, and drops the dedicated
`./executor` export — consumers go through `./client` for everything
client-side.

Package-level changes:
- DELETE `packages/builtin-tool-gtd/` entirely.
- `packages/builtin-tool-lobe-agent/`
  - Move `src/executor/` → `src/client/executor/`. Drop `./executor` from
    `package.json` exports; expose `lobeAgentExecutor` via `./client` only.
  - Rename `GTDExecutionRuntime` → `PlanExecutionRuntime` and place under
    `src/client/executor/PlanRuntime/`. Re-export from package root so the
    server runtime can consume it without pulling in client UI deps.
  - Extend `LobeAgentExecutor` with `createPlan` / `updatePlan` /
    `createTodos` / `updateTodos` / `clearTodos`, all delegated to the
    shared runtime.
  - Add Plan + Todo API entries to the manifest (with their original
    descriptions, humanIntervention, renderDisplayControl).
  - Move all GTD client UI verbatim:
    `Inspector/{ClearTodos,CreatePlan,CreateTodos,UpdatePlan,UpdateTodos}`,
    `Render/{CreatePlan,TodoList}`, `Streaming/CreatePlan`,
    `Intervention/{AddTodo,ClearTodos,CreatePlan}`,
    `components/SortableTodoList`. Register them in
    `LobeAgentInspectors / Renders / Streamings`, add new
    `LobeAgentInterventions`.
  - Merge GTD system role into lobe-agent's (`<plan_and_todos>` plus the
    existing `<sub_agents>` and `<run_in_client>` sections).
  - `package.json`: pick up `@lobechat/prompts` dep and `@lobehub/editor` +
    `antd` + `lucide-react` peer-deps inherited from GTD.

Central registries (`packages/builtin-tools/src/*`) and consumers:
- Remove every `GTDManifest / Inspectors / Renders / Streamings /
  Interventions` import + registration; existing `LobeAgent*` registrations
  now cover them.
- Replace `[GTDManifest.identifier]: GTDInterventions` with
  `[LobeAgentManifest.identifier]: LobeAgentInterventions`.
- Drop `@lobechat/builtin-tool-gtd` workspace dep from
  `packages/builtin-tools/package.json`, `packages/builtin-agents/package.json`
  and root `package.json`.
- Remove `gtdExecutor` from `src/store/tool/slices/builtin/executors/index.ts`;
  switch `lobeAgentExecutor` import to `/client`.
- Replace `serverRuntimes/gtd.ts` with a service factory
  `serverRuntimes/lobeAgentPlan.ts` (`createServerPlanRuntimeService`).
  `serverRuntimes/lobeAgent.ts` instantiates `PlanExecutionRuntime` with
  that service so the registry exposes one runtime per `lobe-agent`
  identifier covering both visual analysis and plan/todo.
- `services/chat/mecha/contextEngineering.ts`: gate plan/todo injection on
  `LobeAgentIdentifier` instead of `GTDIdentifier`.
- `agentConfigResolver.test.ts`: switch fixture plugin IDs to
  `LobeAgentIdentifier`.
- `packages/const/src/recommendedSkill.ts`: drop the standalone `lobe-gtd`
  recommendation — `lobe-agent` already covers it via `defaultToolIds`.

i18n migration (default + zh-CN + en-US; other locales regenerate on
`pnpm i18n`):
- `builtins.lobe-gtd.*` → `builtins.lobe-agent.*` in `plugin.ts/json`.
- `lobe-gtd.*` (tool namespace) → `lobe-agent.*` in `tool.ts/json`.
- Remove `tools.builtins.lobe-gtd.{description,readme,title}` from
  `setting.ts/json` (lobe-agent has its own meta now).
- Update all client component `t(...)` keys to the new namespace.

Mocks / fixtures / tests:
- `packages/agent-mock/src/cases/builtins/todo-write-stress.ts`: all
  `identifier: 'lobe-gtd'` → `'lobe-agent'`; helper comments updated.
- `packages/types/src/stepContext.ts`: comment refers to
  `builtin-tool-lobe-agent` (the only consumer of `StepContextTodoItem`).
- `packages/model-runtime/src/core/streams/google/google-ai.test.ts`:
  function-call names from `lobe-gtd____createPlan` etc. → `lobe-agent____*`.
- `src/store/chat/slices/message/selectors/dbMessage.test.ts`: same.
- `src/features/DevPanel/RenderGallery/fixtures/lobe-gtd.ts` deleted; its
  plan/todo fixtures are folded into `fixtures/lobe-agent.ts` alongside the
  existing `callSubAgent[s]` ones.
- Replace `console.log` → `console.info` in moved client components to
  satisfy lobe-agent's stricter ESLint rules (GTD package allowed
  `console.log`; lobe-agent inherits the repo-wide `no-console` rule).

No behavior change for end users: `lobe-agent` now owns all the APIs,
identifiers, and UI that previously lived in `lobe-gtd`, but as a single
consolidated package under a single tool identifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arvin Xu
2026-05-12 16:12:15 +08:00
parent 0cb972b091
commit 9ca5c9d118
80 changed files with 1060 additions and 1636 deletions
+11 -14
View File
@@ -76,6 +76,17 @@
"builtins.lobe-agent.apiName.callSubAgent.completed": "Sub-agent dispatched: ",
"builtins.lobe-agent.apiName.callSubAgent.loading": "Dispatching sub-agent: ",
"builtins.lobe-agent.apiName.callSubAgents": "Call sub-agents",
"builtins.lobe-agent.apiName.clearTodos": "Clear todos",
"builtins.lobe-agent.apiName.clearTodos.modeAll": "all",
"builtins.lobe-agent.apiName.clearTodos.modeCompleted": "completed",
"builtins.lobe-agent.apiName.clearTodos.result": "Clear <mode>{{mode}}</mode> todos",
"builtins.lobe-agent.apiName.createPlan": "Create plan",
"builtins.lobe-agent.apiName.createPlan.result": "Create plan: <goal>{{goal}}</goal>",
"builtins.lobe-agent.apiName.createTodos": "Create todos",
"builtins.lobe-agent.apiName.updatePlan": "Update plan",
"builtins.lobe-agent.apiName.updatePlan.completed": "Completed",
"builtins.lobe-agent.apiName.updatePlan.modified": "Modified",
"builtins.lobe-agent.apiName.updateTodos": "Update todos",
"builtins.lobe-agent.title": "Lobe Agent",
"builtins.lobe-claude-code.agent.instruction": "Instruction",
"builtins.lobe-claude-code.agent.result": "Result",
@@ -144,20 +155,6 @@
"builtins.lobe-group-management.inspector.executeAgentTasks.title": "Assigning tasks to:",
"builtins.lobe-group-management.inspector.speak.title": "Designated Agent speaks:",
"builtins.lobe-group-management.title": "Group Coordinator",
"builtins.lobe-gtd.apiName.clearTodos": "Clear todos",
"builtins.lobe-gtd.apiName.clearTodos.modeAll": "all",
"builtins.lobe-gtd.apiName.clearTodos.modeCompleted": "completed",
"builtins.lobe-gtd.apiName.clearTodos.result": "Clear <mode>{{mode}}</mode> todos",
"builtins.lobe-gtd.apiName.completeTodos": "Complete todos",
"builtins.lobe-gtd.apiName.createPlan": "Create plan",
"builtins.lobe-gtd.apiName.createPlan.result": "Create plan: <goal>{{goal}}</goal>",
"builtins.lobe-gtd.apiName.createTodos": "Create todos",
"builtins.lobe-gtd.apiName.removeTodos": "Delete todos",
"builtins.lobe-gtd.apiName.updatePlan": "Update plan",
"builtins.lobe-gtd.apiName.updatePlan.completed": "Completed",
"builtins.lobe-gtd.apiName.updatePlan.modified": "Modified",
"builtins.lobe-gtd.apiName.updateTodos": "Update todos",
"builtins.lobe-gtd.title": "Task Tools",
"builtins.lobe-knowledge-base.apiName.readKnowledge": "Read Library content",
"builtins.lobe-knowledge-base.apiName.searchKnowledgeBase": "Search Library",
"builtins.lobe-knowledge-base.inspector.andMoreFiles": "and {{count}} more",
-3
View File
@@ -934,9 +934,6 @@
"tools.builtins.lobe-group-agent-builder.title": "Group Agent Builder",
"tools.builtins.lobe-group-management.description": "Orchestrate and manage multi-agent group conversations",
"tools.builtins.lobe-group-management.title": "Group Management",
"tools.builtins.lobe-gtd.description": "Plan goals and track progress with GTD methodology",
"tools.builtins.lobe-gtd.readme": "Plan goals and track progress using GTD methodology. Create strategic plans, manage todo lists with status tracking, and execute long-running async tasks.",
"tools.builtins.lobe-gtd.title": "GTD Tools",
"tools.builtins.lobe-knowledge-base.description": "Search uploaded documents and domain knowledge via semantic vector search — for persistent, reusable reference",
"tools.builtins.lobe-knowledge-base.title": "Knowledge Base",
"tools.builtins.lobe-local-system.description": "Access and manage local files, run shell commands on your desktop",
+45 -45
View File
@@ -56,51 +56,51 @@
"dalle.generating": "Generating...",
"dalle.images": "Images:",
"dalle.prompt": "Prompt",
"lobe-gtd.actions.add": "Add",
"lobe-gtd.actions.clearCompleted": "Clear Completed",
"lobe-gtd.actions.placeholder": "Enter a to-do item...",
"lobe-gtd.addTodo.placeholder": "Add a todo item...",
"lobe-gtd.clearTodos.cleared": "{{count}} item(s) cleared",
"lobe-gtd.clearTodos.clearedCompleted": "{{count}} completed item(s) cleared",
"lobe-gtd.clearTodos.clearedCompleted_one": "{{count}} completed item cleared",
"lobe-gtd.clearTodos.clearedCompleted_other": "{{count}} completed items cleared",
"lobe-gtd.clearTodos.cleared_one": "{{count}} item cleared",
"lobe-gtd.clearTodos.cleared_other": "{{count}} items cleared",
"lobe-gtd.clearTodos.header": "Clear Todo Items",
"lobe-gtd.clearTodos.label": "Choose what to clear:",
"lobe-gtd.clearTodos.noItems": "No items to clear",
"lobe-gtd.clearTodos.option.all": "Clear all items (including pending)",
"lobe-gtd.clearTodos.option.completed": "Clear completed items only",
"lobe-gtd.clearTodos.remaining": "{{count}} item(s) remaining",
"lobe-gtd.clearTodos.remaining_one": "{{count}} item remaining",
"lobe-gtd.clearTodos.remaining_other": "{{count}} items remaining",
"lobe-gtd.completeTodos.completed": "{{count}} item(s) completed",
"lobe-gtd.completeTodos.completed_one": "{{count}} item completed",
"lobe-gtd.completeTodos.completed_other": "{{count}} items completed",
"lobe-gtd.createPlan.context.label": "Context (optional)",
"lobe-gtd.createPlan.context.placeholder": "Background, constraints, considerations...",
"lobe-gtd.createPlan.description.label": "Description",
"lobe-gtd.createPlan.description.placeholder": "Brief summary of the plan",
"lobe-gtd.createPlan.goal.label": "Goal",
"lobe-gtd.createPlan.goal.placeholder": "What do you want to achieve?",
"lobe-gtd.createTodos.created": "{{count}} to-do item(s) created",
"lobe-gtd.createTodos.created_one": "{{count}} to-do item created",
"lobe-gtd.createTodos.created_other": "{{count}} to-do items created",
"lobe-gtd.createTodos.total": "Total: {{count}} item(s)",
"lobe-gtd.createTodos.total_one": "Total: {{count}} item",
"lobe-gtd.createTodos.total_other": "Total: {{count}} items",
"lobe-gtd.removeTodos.removed": "{{count}} item(s) removed",
"lobe-gtd.removeTodos.removed_one": "{{count}} item removed",
"lobe-gtd.removeTodos.removed_other": "{{count}} items removed",
"lobe-gtd.status.done": "{{count}} completed",
"lobe-gtd.status.pending": "{{count}} pending",
"lobe-gtd.todoItem.placeholder": "Enter todo item...",
"lobe-gtd.todoList.empty": "To-do list is empty",
"lobe-gtd.todoList.items": "{{count}} item(s)",
"lobe-gtd.todoList.items_one": "{{count}} item",
"lobe-gtd.todoList.items_other": "{{count}} items",
"lobe-gtd.todoList.title": "To-Do List",
"lobe-gtd.updateTodos.updated": "To-do list updated",
"lobe-agent.actions.add": "Add",
"lobe-agent.actions.clearCompleted": "Clear Completed",
"lobe-agent.actions.placeholder": "Enter a to-do item...",
"lobe-agent.addTodo.placeholder": "Add a todo item...",
"lobe-agent.clearTodos.cleared": "{{count}} item(s) cleared",
"lobe-agent.clearTodos.clearedCompleted": "{{count}} completed item(s) cleared",
"lobe-agent.clearTodos.clearedCompleted_one": "{{count}} completed item cleared",
"lobe-agent.clearTodos.clearedCompleted_other": "{{count}} completed items cleared",
"lobe-agent.clearTodos.cleared_one": "{{count}} item cleared",
"lobe-agent.clearTodos.cleared_other": "{{count}} items cleared",
"lobe-agent.clearTodos.header": "Clear Todo Items",
"lobe-agent.clearTodos.label": "Choose what to clear:",
"lobe-agent.clearTodos.noItems": "No items to clear",
"lobe-agent.clearTodos.option.all": "Clear all items (including pending)",
"lobe-agent.clearTodos.option.completed": "Clear completed items only",
"lobe-agent.clearTodos.remaining": "{{count}} item(s) remaining",
"lobe-agent.clearTodos.remaining_one": "{{count}} item remaining",
"lobe-agent.clearTodos.remaining_other": "{{count}} items remaining",
"lobe-agent.completeTodos.completed": "{{count}} item(s) completed",
"lobe-agent.completeTodos.completed_one": "{{count}} item completed",
"lobe-agent.completeTodos.completed_other": "{{count}} items completed",
"lobe-agent.createPlan.context.label": "Context (optional)",
"lobe-agent.createPlan.context.placeholder": "Background, constraints, considerations...",
"lobe-agent.createPlan.description.label": "Description",
"lobe-agent.createPlan.description.placeholder": "Brief summary of the plan",
"lobe-agent.createPlan.goal.label": "Goal",
"lobe-agent.createPlan.goal.placeholder": "What do you want to achieve?",
"lobe-agent.createTodos.created": "{{count}} to-do item(s) created",
"lobe-agent.createTodos.created_one": "{{count}} to-do item created",
"lobe-agent.createTodos.created_other": "{{count}} to-do items created",
"lobe-agent.createTodos.total": "Total: {{count}} item(s)",
"lobe-agent.createTodos.total_one": "Total: {{count}} item",
"lobe-agent.createTodos.total_other": "Total: {{count}} items",
"lobe-agent.removeTodos.removed": "{{count}} item(s) removed",
"lobe-agent.removeTodos.removed_one": "{{count}} item removed",
"lobe-agent.removeTodos.removed_other": "{{count}} items removed",
"lobe-agent.status.done": "{{count}} completed",
"lobe-agent.status.pending": "{{count}} pending",
"lobe-agent.todoItem.placeholder": "Enter todo item...",
"lobe-agent.todoList.empty": "To-do list is empty",
"lobe-agent.todoList.items": "{{count}} item(s)",
"lobe-agent.todoList.items_one": "{{count}} item",
"lobe-agent.todoList.items_other": "{{count}} items",
"lobe-agent.todoList.title": "To-Do List",
"lobe-agent.updateTodos.updated": "To-do list updated",
"lobe-knowledge-base.readKnowledge.meta.chars": "Character Count",
"lobe-knowledge-base.readKnowledge.meta.lines": "Line Count",
"localFiles.editFile.newString": "Replace with",
+11 -14
View File
@@ -76,6 +76,17 @@
"builtins.lobe-agent.apiName.callSubAgent.completed": "已派发子代理:",
"builtins.lobe-agent.apiName.callSubAgent.loading": "正在派发子代理:",
"builtins.lobe-agent.apiName.callSubAgents": "调用多个子代理",
"builtins.lobe-agent.apiName.clearTodos": "清除待办",
"builtins.lobe-agent.apiName.clearTodos.modeAll": "全部",
"builtins.lobe-agent.apiName.clearTodos.modeCompleted": "已完成",
"builtins.lobe-agent.apiName.clearTodos.result": "清除<mode>{{mode}}</mode>待办",
"builtins.lobe-agent.apiName.createPlan": "创建计划",
"builtins.lobe-agent.apiName.createPlan.result": "创建计划:<goal>{{goal}}</goal>",
"builtins.lobe-agent.apiName.createTodos": "创建待办",
"builtins.lobe-agent.apiName.updatePlan": "更新计划",
"builtins.lobe-agent.apiName.updatePlan.completed": "已完成",
"builtins.lobe-agent.apiName.updatePlan.modified": "已修改",
"builtins.lobe-agent.apiName.updateTodos": "更新待办",
"builtins.lobe-agent.title": "Lobe Agent",
"builtins.lobe-claude-code.agent.instruction": "指令",
"builtins.lobe-claude-code.agent.result": "结果",
@@ -144,20 +155,6 @@
"builtins.lobe-group-management.inspector.executeAgentTasks.title": "分配任务给:",
"builtins.lobe-group-management.inspector.speak.title": "指定 Agent 发言:",
"builtins.lobe-group-management.title": "群组协调",
"builtins.lobe-gtd.apiName.clearTodos": "清除待办",
"builtins.lobe-gtd.apiName.clearTodos.modeAll": "全部",
"builtins.lobe-gtd.apiName.clearTodos.modeCompleted": "已完成",
"builtins.lobe-gtd.apiName.clearTodos.result": "清除<mode>{{mode}}</mode>待办",
"builtins.lobe-gtd.apiName.completeTodos": "完成待办",
"builtins.lobe-gtd.apiName.createPlan": "创建计划",
"builtins.lobe-gtd.apiName.createPlan.result": "创建计划:<goal>{{goal}}</goal>",
"builtins.lobe-gtd.apiName.createTodos": "创建待办",
"builtins.lobe-gtd.apiName.removeTodos": "删除待办",
"builtins.lobe-gtd.apiName.updatePlan": "更新计划",
"builtins.lobe-gtd.apiName.updatePlan.completed": "已完成",
"builtins.lobe-gtd.apiName.updatePlan.modified": "已修改",
"builtins.lobe-gtd.apiName.updateTodos": "更新待办",
"builtins.lobe-gtd.title": "任务工具",
"builtins.lobe-knowledge-base.apiName.readKnowledge": "读取资源库内容",
"builtins.lobe-knowledge-base.apiName.searchKnowledgeBase": "搜索资源库",
"builtins.lobe-knowledge-base.inspector.andMoreFiles": "还有 {{count}} 个",
-3
View File
@@ -934,9 +934,6 @@
"tools.builtins.lobe-group-agent-builder.title": "群组助手构建器",
"tools.builtins.lobe-group-management.description": "编排并管理多助手群组对话",
"tools.builtins.lobe-group-management.title": "群组管理",
"tools.builtins.lobe-gtd.description": "使用 GTD 方法规划目标并追踪进度",
"tools.builtins.lobe-gtd.readme": "使用 GTD 方法规划目标并追踪进度。创建战略计划、管理带状态跟踪的待办列表,并执行长时间运行的异步任务。",
"tools.builtins.lobe-gtd.title": "GTD 工具",
"tools.builtins.lobe-knowledge-base.description": "通过语义向量检索搜索已上传的文档与领域知识 — 适用于持久、可复用的参考资料",
"tools.builtins.lobe-knowledge-base.title": "知识库",
"tools.builtins.lobe-local-system.description": "访问和管理本地文件,在桌面端运行 Shell 命令",
+45 -45
View File
@@ -56,51 +56,51 @@
"dalle.generating": "生成中...",
"dalle.images": "图片:",
"dalle.prompt": "提示词",
"lobe-gtd.actions.add": "添加",
"lobe-gtd.actions.clearCompleted": "清除已完成",
"lobe-gtd.actions.placeholder": "输入待办事项...",
"lobe-gtd.addTodo.placeholder": "添加待办事项...",
"lobe-gtd.clearTodos.cleared": "已清除 {{count}} 项",
"lobe-gtd.clearTodos.clearedCompleted": "已清除 {{count}} 项已完成事项",
"lobe-gtd.clearTodos.clearedCompleted_one": "已清除 {{count}} 项已完成事项",
"lobe-gtd.clearTodos.clearedCompleted_other": "已清除 {{count}} 项已完成事项",
"lobe-gtd.clearTodos.cleared_one": "已清除 {{count}} 项",
"lobe-gtd.clearTodos.cleared_other": "已清除 {{count}} 项",
"lobe-gtd.clearTodos.header": "清除待办事项",
"lobe-gtd.clearTodos.label": "请选择要清除的内容:",
"lobe-gtd.clearTodos.noItems": "无可清除事项",
"lobe-gtd.clearTodos.option.all": "清除所有事项(包括未完成)",
"lobe-gtd.clearTodos.option.completed": "仅清除已完成事项",
"lobe-gtd.clearTodos.remaining": "剩余 {{count}} 项",
"lobe-gtd.clearTodos.remaining_one": "剩余 {{count}} 项",
"lobe-gtd.clearTodos.remaining_other": "剩余 {{count}} 项",
"lobe-gtd.completeTodos.completed": "已完成 {{count}} 项",
"lobe-gtd.completeTodos.completed_one": "已完成 {{count}} 项",
"lobe-gtd.completeTodos.completed_other": "已完成 {{count}} 项",
"lobe-gtd.createPlan.context.label": "上下文(可选)",
"lobe-gtd.createPlan.context.placeholder": "背景、限制、注意事项等...",
"lobe-gtd.createPlan.description.label": "描述",
"lobe-gtd.createPlan.description.placeholder": "简要说明计划内容",
"lobe-gtd.createPlan.goal.label": "目标",
"lobe-gtd.createPlan.goal.placeholder": "你希望达成什么目标?",
"lobe-gtd.createTodos.created": "已创建 {{count}} 项待办事项",
"lobe-gtd.createTodos.created_one": "已创建 {{count}} 项待办事项",
"lobe-gtd.createTodos.created_other": "已创建 {{count}} 项待办事项",
"lobe-gtd.createTodos.total": "总计:{{count}} 项",
"lobe-gtd.createTodos.total_one": "总计:{{count}} 项",
"lobe-gtd.createTodos.total_other": "总计:{{count}} 项",
"lobe-gtd.removeTodos.removed": "已移除 {{count}} 项",
"lobe-gtd.removeTodos.removed_one": "已移除 {{count}} 项",
"lobe-gtd.removeTodos.removed_other": "已移除 {{count}} 项",
"lobe-gtd.status.done": "已完成 {{count}} 项",
"lobe-gtd.status.pending": "待完成 {{count}} 项",
"lobe-gtd.todoItem.placeholder": "输入待办事项...",
"lobe-gtd.todoList.empty": "待办事项列表为空",
"lobe-gtd.todoList.items": "{{count}} 项",
"lobe-gtd.todoList.items_one": "{{count}} 项",
"lobe-gtd.todoList.items_other": "{{count}} 项",
"lobe-gtd.todoList.title": "待办事项列表",
"lobe-gtd.updateTodos.updated": "待办事项已更新",
"lobe-agent.actions.add": "添加",
"lobe-agent.actions.clearCompleted": "清除已完成",
"lobe-agent.actions.placeholder": "输入待办事项...",
"lobe-agent.addTodo.placeholder": "添加待办事项...",
"lobe-agent.clearTodos.cleared": "已清除 {{count}} 项",
"lobe-agent.clearTodos.clearedCompleted": "已清除 {{count}} 项已完成事项",
"lobe-agent.clearTodos.clearedCompleted_one": "已清除 {{count}} 项已完成事项",
"lobe-agent.clearTodos.clearedCompleted_other": "已清除 {{count}} 项已完成事项",
"lobe-agent.clearTodos.cleared_one": "已清除 {{count}} 项",
"lobe-agent.clearTodos.cleared_other": "已清除 {{count}} 项",
"lobe-agent.clearTodos.header": "清除待办事项",
"lobe-agent.clearTodos.label": "请选择要清除的内容:",
"lobe-agent.clearTodos.noItems": "无可清除事项",
"lobe-agent.clearTodos.option.all": "清除所有事项(包括未完成)",
"lobe-agent.clearTodos.option.completed": "仅清除已完成事项",
"lobe-agent.clearTodos.remaining": "剩余 {{count}} 项",
"lobe-agent.clearTodos.remaining_one": "剩余 {{count}} 项",
"lobe-agent.clearTodos.remaining_other": "剩余 {{count}} 项",
"lobe-agent.completeTodos.completed": "已完成 {{count}} 项",
"lobe-agent.completeTodos.completed_one": "已完成 {{count}} 项",
"lobe-agent.completeTodos.completed_other": "已完成 {{count}} 项",
"lobe-agent.createPlan.context.label": "上下文(可选)",
"lobe-agent.createPlan.context.placeholder": "背景、限制、注意事项等...",
"lobe-agent.createPlan.description.label": "描述",
"lobe-agent.createPlan.description.placeholder": "简要说明计划内容",
"lobe-agent.createPlan.goal.label": "目标",
"lobe-agent.createPlan.goal.placeholder": "你希望达成什么目标?",
"lobe-agent.createTodos.created": "已创建 {{count}} 项待办事项",
"lobe-agent.createTodos.created_one": "已创建 {{count}} 项待办事项",
"lobe-agent.createTodos.created_other": "已创建 {{count}} 项待办事项",
"lobe-agent.createTodos.total": "总计:{{count}} 项",
"lobe-agent.createTodos.total_one": "总计:{{count}} 项",
"lobe-agent.createTodos.total_other": "总计:{{count}} 项",
"lobe-agent.removeTodos.removed": "已移除 {{count}} 项",
"lobe-agent.removeTodos.removed_one": "已移除 {{count}} 项",
"lobe-agent.removeTodos.removed_other": "已移除 {{count}} 项",
"lobe-agent.status.done": "已完成 {{count}} 项",
"lobe-agent.status.pending": "待完成 {{count}} 项",
"lobe-agent.todoItem.placeholder": "输入待办事项...",
"lobe-agent.todoList.empty": "待办事项列表为空",
"lobe-agent.todoList.items": "{{count}} 项",
"lobe-agent.todoList.items_one": "{{count}} 项",
"lobe-agent.todoList.items_other": "{{count}} 项",
"lobe-agent.todoList.title": "待办事项列表",
"lobe-agent.updateTodos.updated": "待办事项已更新",
"lobe-knowledge-base.readKnowledge.meta.chars": "字符数",
"lobe-knowledge-base.readKnowledge.meta.lines": "行数",
"localFiles.editFile.newString": "替换为",
-1
View File
@@ -220,7 +220,6 @@
"@lobechat/builtin-tool-creds": "workspace:*",
"@lobechat/builtin-tool-group-agent-builder": "workspace:*",
"@lobechat/builtin-tool-group-management": "workspace:*",
"@lobechat/builtin-tool-gtd": "workspace:*",
"@lobechat/builtin-tool-knowledge-base": "workspace:*",
"@lobechat/builtin-tool-lobe-agent": "workspace:*",
"@lobechat/builtin-tool-local-system": "workspace:*",
@@ -1,13 +1,13 @@
import { defineCase, errorStep, llmStep, toolStep } from '../../builders/defineCase';
// ---------------------------------------------------------------------------
// Helpers — all mapped to lobe-gtd
// Helpers — all mapped to lobe-agent
// ---------------------------------------------------------------------------
/** lobe-gtd / createTodos */
/** lobe-agent / createTodos */
const createTodos = (items: string[], durationMs = 60) =>
toolStep({
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'createTodos',
arguments: JSON.stringify({ adds: items }),
result: {
@@ -20,14 +20,14 @@ const createTodos = (items: string[], durationMs = 60) =>
durationMs,
});
/** lobe-gtd / updateTodos — batch operations */
/** lobe-agent / updateTodos — batch operations */
const updateTodos = (
operations: Array<{ type: string; index?: number; newText?: string; status?: string }>,
currentItems: Array<{ text: string; status: string }>,
durationMs = 60,
) =>
toolStep({
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'updateTodos',
arguments: JSON.stringify({ operations }),
result: {
@@ -40,7 +40,7 @@ const updateTodos = (
durationMs,
});
/** lobe-gtd / createPlan */
/** lobe-agent / createPlan */
const createPlan = (
goal: string,
description: string,
@@ -49,7 +49,7 @@ const createPlan = (
durationMs = 80,
) =>
toolStep({
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'createPlan',
arguments: JSON.stringify({ goal, description, context }),
result: {
@@ -66,14 +66,14 @@ const createPlan = (
durationMs,
});
/** lobe-gtd / updatePlan */
/** lobe-agent / updatePlan */
const updatePlan = (
planId: string,
set: { goal?: string; description?: string; context?: string; completed?: boolean },
durationMs = 60,
) =>
toolStep({
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'updatePlan',
arguments: JSON.stringify({ planId, ...set }),
result: {
@@ -108,7 +108,7 @@ const callSubAgent = (description: string, instruction: string, durationMs = 200
const breathe = (text: string, durationMs = 250) => llmStep({ text, durationMs });
// ---------------------------------------------------------------------------
// The main case — ~200 lobe-gtd tool calls across 8 phases
// The main case — ~200 lobe-agent tool calls across 8 phases
// ---------------------------------------------------------------------------
export const todoWriteStress = defineCase({
@@ -137,8 +137,8 @@ export const todoWriteStress = defineCase({
text: '第一阶段:全面盘点现有代码结构。创建总体计划,再拆解为 15 个待办事项。',
reasoning: '先创建一个顶层计划文档,再将盘点工作拆解为具体的 todo 项。',
toolsCalling: [
{ id: 'tc-plan-1', identifier: 'lobe-gtd', apiName: 'createPlan', arguments: '{}' },
{ id: 'tc-todos-1', identifier: 'lobe-gtd', apiName: 'createTodos', arguments: '{}' },
{ id: 'tc-plan-1', identifier: 'lobe-agent', apiName: 'createPlan', arguments: '{}' },
{ id: 'tc-todos-1', identifier: 'lobe-agent', apiName: 'createTodos', arguments: '{}' },
],
durationMs: 600,
}),
-1
View File
@@ -9,7 +9,6 @@
"@lobechat/builtin-tool-agent-management": "workspace:*",
"@lobechat/builtin-tool-group-agent-builder": "workspace:*",
"@lobechat/builtin-tool-group-management": "workspace:*",
"@lobechat/builtin-tool-gtd": "workspace:*",
"@lobechat/builtin-tool-notebook": "workspace:*",
"@lobechat/builtin-tool-task": "workspace:*",
"@lobechat/builtin-tool-user-interaction": "workspace:*",
-27
View File
@@ -1,27 +0,0 @@
{
"name": "@lobechat/builtin-tool-gtd",
"version": "1.0.0",
"private": true,
"exports": {
".": "./src/index.ts",
"./executor": "./src/executor/index.ts",
"./executionRuntime": "./src/ExecutionRuntime/index.ts",
"./client": "./src/client/index.ts"
},
"main": "./src/index.ts",
"dependencies": {
"@lobechat/const": "workspace:*",
"@lobechat/prompts": "workspace:*"
},
"devDependencies": {
"@lobechat/types": "workspace:*"
},
"peerDependencies": {
"@lobehub/editor": "^4",
"@lobehub/ui": "^5",
"antd": "^6",
"antd-style": "*",
"lucide-react": "*",
"react": "*"
}
}
@@ -1,22 +0,0 @@
import type { BuiltinInspector } from '@lobechat/types';
import { GTDApiName } from '../../types';
import { ClearTodosInspector } from './ClearTodos';
import { CreatePlanInspector } from './CreatePlan';
import { CreateTodosInspector } from './CreateTodos';
import { UpdatePlanInspector } from './UpdatePlan';
import { UpdateTodosInspector } from './UpdateTodos';
/**
* GTD Inspector Components Registry
*
* Inspector components customize the title/header area
* of tool calls in the conversation UI.
*/
export const GTDInspectors: Record<string, BuiltinInspector> = {
[GTDApiName.clearTodos]: ClearTodosInspector as BuiltinInspector,
[GTDApiName.createPlan]: CreatePlanInspector as BuiltinInspector,
[GTDApiName.createTodos]: CreateTodosInspector as BuiltinInspector,
[GTDApiName.updatePlan]: UpdatePlanInspector as BuiltinInspector,
[GTDApiName.updateTodos]: UpdateTodosInspector as BuiltinInspector,
};
@@ -1,25 +0,0 @@
import { GTDApiName } from '../../types';
import CreatePlan from './CreatePlan';
import TodoListRender from './TodoList';
/**
* GTD Tool Render Components Registry
*
* All todo operations use the same TodoList render component
* which displays the current state of the todo list.
* Plan operations use the CreatePlan render component.
*/
export const GTDRenders = {
// All todo operations render the same TodoList UI
[GTDApiName.clearTodos]: TodoListRender,
[GTDApiName.createTodos]: TodoListRender,
[GTDApiName.updateTodos]: TodoListRender,
// Plan operations render the PlanCard UI
[GTDApiName.createPlan]: CreatePlan,
[GTDApiName.updatePlan]: CreatePlan,
};
export { default as CreatePlan, PlanCard } from './CreatePlan';
export type { TodoListRenderState } from './TodoList';
export { default as TodoListRender, TodoListUI } from './TodoList';
@@ -1,16 +0,0 @@
import type { BuiltinStreaming } from '@lobechat/types';
import { GTDApiName } from '../../types';
import { CreatePlanStreaming } from './CreatePlan';
/**
* GTD Streaming Components Registry
*
* Streaming components render tool calls while they are
* still executing, allowing real-time feedback to users.
*/
export const GTDStreamings: Record<string, BuiltinStreaming> = {
[GTDApiName.createPlan]: CreatePlanStreaming as BuiltinStreaming,
};
export { CreatePlanStreaming } from './CreatePlan';
@@ -1,20 +0,0 @@
// Inspector components (customized tool call headers)
export { GTDInspectors } from './Inspector';
// Render components (read-only snapshots)
export type { TodoListRenderState } from './Render';
export { GTDRenders, TodoListRender, TodoListUI } from './Render';
// Streaming components (real-time tool execution feedback)
export { CreatePlanStreaming, GTDStreamings } from './Streaming';
// Intervention components (interactive editing)
export { AddTodoIntervention, ClearTodosIntervention, GTDInterventions } from './Intervention';
// Reusable components
export type { SortableTodoListProps, TodoListItem } from './components';
export { SortableTodoList } from './components';
// Re-export types and manifest for convenience
export { GTDManifest } from '../manifest';
export * from '../types';
@@ -1,417 +0,0 @@
import type { BuiltinToolContext } from '@lobechat/types';
import { describe, expect, it } from 'vitest';
import { gtdExecutor } from './index';
describe('GTDExecutor', () => {
const createMockContext = (pluginState?: Record<string, unknown>): BuiltinToolContext => ({
messageId: 'test-message-id',
operationId: 'test-operation-id',
pluginState,
});
describe('createTodos', () => {
it('should add items to empty todo list using adds (AI input)', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.createTodos({ adds: ['Buy milk', 'Call mom'] }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('Added 2 items');
expect(result.content).toContain('Buy milk');
expect(result.content).toContain('Call mom');
expect(result.state?.todos.items).toHaveLength(2);
expect(result.state?.todos.items[0].text).toBe('Buy milk');
expect(result.state?.todos.items[0].status).toBe('todo');
expect(result.state?.todos.items[1].text).toBe('Call mom');
});
it('should add items using items (user-edited format)', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.createTodos(
{
items: [
{ text: 'Buy milk', status: 'todo' },
{ text: 'Call mom', status: 'completed' },
],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.content).toContain('Added 2 items');
expect(result.state?.todos.items).toHaveLength(2);
expect(result.state?.todos.items[0].text).toBe('Buy milk');
expect(result.state?.todos.items[0].status).toBe('todo');
expect(result.state?.todos.items[1].text).toBe('Call mom');
expect(result.state?.todos.items[1].status).toBe('completed');
});
it('should append items to existing todo list', async () => {
const ctx = createMockContext({
todos: {
items: [{ text: 'Existing task', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.createTodos({ adds: ['New task'] }, ctx);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(2);
expect(result.state?.todos.items[0].text).toBe('Existing task');
expect(result.state?.todos.items[1].text).toBe('New task');
});
it('should return error when no items provided', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.createTodos({ adds: [] }, ctx);
expect(result.success).toBe(false);
expect(result.content).toContain('No items provided');
});
it('should add single item with correct singular grammar', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.createTodos({ adds: ['Single task'] }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('Added 1 item');
expect(result.content).not.toContain('items');
});
it('should prioritize items over adds when both provided', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.createTodos(
{
adds: ['AI task'],
items: [{ text: 'User edited task', status: 'completed' }],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(1);
expect(result.state?.todos.items[0].text).toBe('User edited task');
expect(result.state?.todos.items[0].status).toBe('completed');
});
});
describe('updateTodos', () => {
it('should add new items via operations', async () => {
const ctx = createMockContext({
todos: {
items: [{ text: 'Existing task', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.updateTodos(
{
operations: [{ type: 'add', text: 'New task' }],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(2);
expect(result.state?.todos.items[1].text).toBe('New task');
});
it('should update item text via operations', async () => {
const ctx = createMockContext({
todos: {
items: [{ text: 'Old task', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.updateTodos(
{
operations: [{ type: 'update', index: 0, newText: 'Updated task' }],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.state?.todos.items[0].text).toBe('Updated task');
});
it('should complete items via operations', async () => {
const ctx = createMockContext({
todos: {
items: [{ text: 'Task', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.updateTodos(
{
operations: [{ type: 'complete', index: 0 }],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.state?.todos.items[0].status).toBe('completed');
});
it('should remove items via operations', async () => {
const ctx = createMockContext({
todos: {
items: [
{ text: 'Task 1', status: 'todo' },
{ text: 'Task 2', status: 'todo' },
],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.updateTodos(
{
operations: [{ type: 'remove', index: 0 }],
},
ctx,
);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(1);
expect(result.state?.todos.items[0].text).toBe('Task 2');
});
it('should return error when no operations provided', async () => {
const ctx = createMockContext();
const result = await gtdExecutor.updateTodos({ operations: [] }, ctx);
expect(result.success).toBe(false);
expect(result.content).toContain('No operations provided');
});
it('should handle complete operations with out-of-range indices gracefully', async () => {
// Test case: 5 items (indices 0-4), operations reference index 5 (out of range) and index 2 (valid)
const ctx = createMockContext({
createdItems: ['Task A', 'Task B', 'Task C', 'Task D', 'Task E'],
todos: {
items: [
{ status: 'todo', text: 'Task A' },
{ status: 'todo', text: 'Task B' },
{ status: 'todo', text: 'Task C' },
{ status: 'todo', text: 'Task D' },
{ status: 'todo', text: 'Task E' },
],
updatedAt: '2025-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.updateTodos(
{
operations: [
{ index: 5, type: 'complete' }, // out of range
{ index: 2, type: 'complete' }, // valid
],
},
ctx,
);
expect(result.success).toBe(true);
// Should have all 5 items preserved
expect(result.state?.todos.items).toHaveLength(5);
// Index 5 is out of range (0-4), so should be skipped
// Index 2 should be completed
expect(result.state?.todos.items[2].status).toBe('completed');
// Other items should remain uncompleted
expect(result.state?.todos.items[0].status).toBe('todo');
expect(result.state?.todos.items[1].status).toBe('todo');
expect(result.state?.todos.items[3].status).toBe('todo');
expect(result.state?.todos.items[4].status).toBe('todo');
});
});
describe('clearTodos', () => {
it('should clear all items when mode is "all"', async () => {
const ctx = createMockContext({
todos: {
items: [
{ text: 'Task 1', status: 'todo' },
{ text: 'Task 2', status: 'completed' },
],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.clearTodos({ mode: 'all' }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('Cleared all 2 items');
expect(result.state?.todos.items).toHaveLength(0);
});
it('should clear only completed items when mode is "completed"', async () => {
const ctx = createMockContext({
todos: {
items: [
{ text: 'Task 1', status: 'todo' },
{ text: 'Task 2', status: 'completed' },
{ text: 'Task 3', status: 'completed' },
],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.clearTodos({ mode: 'completed' }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('Cleared 2 completed items');
// New format shows "1 pending" instead of "1 item remaining"
expect(result.content).toContain('1 pending');
expect(result.state?.todos.items).toHaveLength(1);
expect(result.state?.todos.items[0].text).toBe('Task 1');
});
it('should handle empty todo list', async () => {
const ctx = createMockContext({
todos: { items: [], updatedAt: '2024-01-01T00:00:00.000Z' },
});
const result = await gtdExecutor.clearTodos({ mode: 'all' }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('already empty');
});
it('should handle no completed items to clear', async () => {
const ctx = createMockContext({
todos: {
items: [{ text: 'Task 1', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
});
const result = await gtdExecutor.clearTodos({ mode: 'completed' }, ctx);
expect(result.success).toBe(true);
expect(result.content).toContain('No completed items to clear');
expect(result.state?.todos.items).toHaveLength(1);
});
});
describe('stepContext priority', () => {
it('should prioritize stepContext.todos over pluginState.todos', async () => {
// Create context with both stepContext and pluginState
const ctx: BuiltinToolContext = {
messageId: 'test-message-id',
operationId: 'test-operation-id',
pluginState: {
todos: {
items: [{ text: 'Old task from pluginState', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
},
stepContext: {
todos: {
items: [{ text: 'New task from stepContext', status: 'completed' }],
updatedAt: '2024-06-01T00:00:00.000Z',
},
},
};
// createTodos should use stepContext.todos as base
const result = await gtdExecutor.createTodos({ adds: ['Another task'] }, ctx);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(2);
// First item should be from stepContext, not pluginState
expect(result.state?.todos.items[0].text).toBe('New task from stepContext');
expect(result.state?.todos.items[0].status).toBe('completed');
expect(result.state?.todos.items[1].text).toBe('Another task');
});
it('should fallback to pluginState.todos when stepContext.todos is undefined', async () => {
const ctx: BuiltinToolContext = {
messageId: 'test-message-id',
operationId: 'test-operation-id',
pluginState: {
todos: {
items: [{ text: 'Task from pluginState', status: 'todo' }],
updatedAt: '2024-01-01T00:00:00.000Z',
},
},
stepContext: {
// No todos in stepContext
},
};
const result = await gtdExecutor.createTodos({ adds: ['New task'] }, ctx);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(2);
expect(result.state?.todos.items[0].text).toBe('Task from pluginState');
expect(result.state?.todos.items[1].text).toBe('New task');
});
it('should start with empty todos when both stepContext and pluginState are empty', async () => {
const ctx: BuiltinToolContext = {
messageId: 'test-message-id',
operationId: 'test-operation-id',
stepContext: {},
};
const result = await gtdExecutor.createTodos({ adds: ['First task'] }, ctx);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(1);
expect(result.state?.todos.items[0].text).toBe('First task');
});
it('should work with stepContext.todos for clearTodos', async () => {
const ctx: BuiltinToolContext = {
messageId: 'test-message-id',
operationId: 'test-operation-id',
stepContext: {
todos: {
items: [
{ text: 'Task 1', status: 'completed' },
{ text: 'Task 2', status: 'todo' },
],
updatedAt: '2024-06-01T00:00:00.000Z',
},
},
};
const result = await gtdExecutor.clearTodos({ mode: 'completed' }, ctx);
expect(result.success).toBe(true);
expect(result.state?.todos.items).toHaveLength(1);
expect(result.state?.todos.items[0].text).toBe('Task 2');
});
});
describe('executor metadata', () => {
it('should have correct identifier', () => {
expect(gtdExecutor.identifier).toBe('lobe-gtd');
});
it('should support all APIs', () => {
expect(gtdExecutor.hasApi('createTodos')).toBe(true);
expect(gtdExecutor.hasApi('updateTodos')).toBe(true);
expect(gtdExecutor.hasApi('clearTodos')).toBe(true);
expect(gtdExecutor.hasApi('createPlan')).toBe(true);
expect(gtdExecutor.hasApi('updatePlan')).toBe(true);
});
it('should return correct API names', () => {
const apiNames = gtdExecutor.getApiNames();
expect(apiNames).toContain('createTodos');
expect(apiNames).toContain('updateTodos');
expect(apiNames).toContain('clearTodos');
expect(apiNames).toContain('createPlan');
expect(apiNames).toContain('updatePlan');
expect(apiNames).toHaveLength(5);
});
});
});
@@ -1,126 +0,0 @@
import type { BuiltinToolContext, BuiltinToolResult } from '@lobechat/types';
import { BaseExecutor } from '@lobechat/types';
import { notebookService } from '@/services/notebook';
import { useNotebookStore } from '@/store/notebook';
import {
GTDExecutionRuntime,
type GTDRuntimeContext,
type GTDRuntimeService,
type PlanDocument,
} from '../ExecutionRuntime';
import { GTDIdentifier } from '../manifest';
import type {
ClearTodosParams,
CreatePlanParams,
CreateTodosParams,
UpdatePlanParams,
UpdateTodosParams,
} from '../types';
import { GTDApiName } from '../types';
import { getTodosFromContext } from './helper';
const PLAN_DOC_TYPE = 'agent/plan';
/**
* Normalize a document payload returned by notebookService / useNotebookStore
* into the `PlanDocument` shape expected by GTDExecutionRuntime.
*/
const normalizePlanDoc = (doc: {
content?: string | null;
createdAt: Date | string;
description?: string | null;
id: string;
metadata?: Record<string, any> | null;
title?: string | null;
updatedAt: Date | string;
}): PlanDocument => ({
content: doc.content ?? null,
createdAt: typeof doc.createdAt === 'string' ? new Date(doc.createdAt) : doc.createdAt,
description: doc.description ?? null,
id: doc.id,
metadata: doc.metadata ?? null,
title: doc.title ?? null,
updatedAt: typeof doc.updatedAt === 'string' ? new Date(doc.updatedAt) : doc.updatedAt,
});
/**
* Client-side implementation of the GTD runtime service.
* Routes user-facing plan CRUD through useNotebookStore (so SWR caches refresh),
* and keeps silent metadata writes (todos sync) on the raw notebookService.
*/
const clientGTDService: GTDRuntimeService = {
createPlan: async ({ topicId, goal, description, content }) => {
const doc = await useNotebookStore.getState().createDocument({
content,
description,
title: goal,
topicId,
type: PLAN_DOC_TYPE,
});
return normalizePlanDoc(doc);
},
findPlanById: async (id) => {
const doc = await notebookService.getDocument(id);
return doc ? normalizePlanDoc(doc) : null;
},
findPlanByTopic: async (topicId) => {
const result = await notebookService.listDocuments({ topicId, type: PLAN_DOC_TYPE });
const first = result.data[0];
return first ? normalizePlanDoc(first) : null;
},
updatePlan: async (id, { goal, description, content }, topicId) => {
const doc = await useNotebookStore
.getState()
.updateDocument({ content, description, id, title: goal }, topicId ?? '');
if (!doc) throw new Error(`Plan not found after update: ${id}`);
return normalizePlanDoc(doc);
},
updatePlanMetadata: async (id, metadata) => {
await notebookService.updateDocument({ id, metadata });
},
};
const GTDApiNameEnum = {
clearTodos: GTDApiName.clearTodos,
createPlan: GTDApiName.createPlan,
createTodos: GTDApiName.createTodos,
updatePlan: GTDApiName.updatePlan,
updateTodos: GTDApiName.updateTodos,
} as const;
const toRuntimeContext = (ctx: BuiltinToolContext): GTDRuntimeContext => ({
currentTodos: getTodosFromContext(ctx),
messageId: ctx.messageId,
signal: ctx.signal,
topicId: ctx.topicId ?? undefined,
});
class GTDExecutor extends BaseExecutor<typeof GTDApiNameEnum> {
readonly identifier = GTDIdentifier;
protected readonly apiEnum = GTDApiNameEnum;
private runtime = new GTDExecutionRuntime(clientGTDService);
createTodos = (params: CreateTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.runtime.createTodos(params, toRuntimeContext(ctx));
updateTodos = (params: UpdateTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.runtime.updateTodos(params, toRuntimeContext(ctx));
clearTodos = (params: ClearTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.runtime.clearTodos(params, toRuntimeContext(ctx));
createPlan = (params: CreatePlanParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.runtime.createPlan(params, toRuntimeContext(ctx));
updatePlan = (params: UpdatePlanParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.runtime.updatePlan(params, toRuntimeContext(ctx));
}
export const gtdExecutor = new GTDExecutor();
-3
View File
@@ -1,3 +0,0 @@
export * from './manifest';
export * from './systemRole';
export * from './types';
-164
View File
@@ -1,164 +0,0 @@
import type { BuiltinToolManifest } from '@lobechat/types';
import { systemPrompt } from './systemRole';
import { GTDApiName } from './types';
export const GTDIdentifier = 'lobe-gtd';
export const GTDManifest: BuiltinToolManifest = {
api: [
// ==================== Planning ====================
{
description:
'Create a high-level plan document. Plans define the strategic direction (the "what" and "why"), while todos handle the actionable steps.',
name: GTDApiName.createPlan,
humanIntervention: 'required',
renderDisplayControl: 'expand',
parameters: {
properties: {
goal: {
description: 'The main goal or objective to achieve (used as document title).',
type: 'string',
},
description: {
description: 'A brief summary of the plan (1-2 sentences).',
type: 'string',
},
context: {
description:
'Detailed context, constraints, background information, or strategic considerations relevant to the goal.',
type: 'string',
},
},
required: ['goal', 'description', 'context'],
type: 'object',
},
},
{
description:
'Update an existing plan document. Only use this when the goal fundamentally changes. Plans should remain stable once created - do not update plans just because details change.',
name: GTDApiName.updatePlan,
parameters: {
properties: {
planId: {
description:
'The document ID of the plan to update (e.g., "docs_xxx"). This ID is returned in the createPlan response. Do NOT use the goal text as planId.',
type: 'string',
},
goal: {
description: 'Updated goal (document title).',
type: 'string',
},
description: {
description: 'Updated brief summary.',
type: 'string',
},
context: {
description: 'Updated detailed context.',
type: 'string',
},
},
required: ['planId'],
type: 'object',
},
},
// ==================== Quick Todo ====================
{
description: 'Create new todo items. Pass an array of text strings.',
name: GTDApiName.createTodos,
humanIntervention: 'required',
parameters: {
properties: {
adds: {
description: 'Array of todo item texts to create.',
items: { type: 'string' },
type: 'array',
},
},
required: ['adds'],
type: 'object',
},
},
{
description: `Update todo items with batch operations. Each operation type requires specific fields:
- "add": requires "text" (the todo text to add)
- "update": requires "index", optional "newText" and/or "status"
- "remove": requires "index" only
- "complete": requires "index" only (marks item as completed)
- "processing": requires "index" only (marks item as in progress)`,
name: GTDApiName.updateTodos,
renderDisplayControl: 'expand',
parameters: {
properties: {
operations: {
description:
'Array of update operations. IMPORTANT: For "complete", "processing" and "remove" operations, only pass "type" and "index" - no other fields needed.',
items: {
properties: {
type: {
description:
'Operation type. "add" needs text, "update" needs index + optional newText/status, "remove", "complete" and "processing" need index only.',
enum: ['add', 'update', 'remove', 'complete', 'processing'],
type: 'string',
},
text: {
description: 'Required for "add" only: the text to add.',
type: 'string',
},
index: {
description:
'Required for "update", "remove", "complete", "processing": the item index (0-based).',
type: 'number',
},
newText: {
description: 'Optional for "update" only: the new text.',
type: 'string',
},
status: {
description:
'Optional for "update" only: set status (todo, processing, completed).',
enum: ['todo', 'processing', 'completed'],
type: 'string',
},
},
required: ['type'],
type: 'object',
},
type: 'array',
},
},
required: ['operations'],
type: 'object',
},
},
{
description: 'Clear todo items. Can clear only completed items or all items.',
name: GTDApiName.clearTodos,
humanIntervention: 'always',
renderDisplayControl: 'expand',
parameters: {
properties: {
mode: {
description: '"completed" clears only done items, "all" clears the entire list.',
enum: ['completed', 'all'],
type: 'string',
},
},
required: ['mode'],
type: 'object',
},
},
],
identifier: GTDIdentifier,
meta: {
avatar: '✅',
description:
'Plan and track one-time goals and manage todo checklists. For goals that need to be done once or tracked manually — NOT for tasks that should repeat automatically on a schedule (use lobe-cron for daily/weekly/recurring automation). To dispatch long-running sub-agent investigations, use the lobe-agent tool.',
title: 'GTD Tools',
},
systemRole: systemPrompt,
type: 'builtin',
};
export { GTDApiName } from './types';
-161
View File
@@ -1,161 +0,0 @@
export const systemPrompt = `You have GTD (Getting Things Done) tools to help manage plans and todos effectively. These tools support two levels of task management:
- **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.
- **Todo**: The concrete execution list with actionable items. Todos define the "how" - specific tasks to accomplish the plan. **Todos are dynamic** - they can be added, updated, completed, and removed as work progresses.
For dispatching long-running, multi-step investigations (web research, deep analysis), use the **lobe-agent** tool's sub-agent capability instead.
<tool_overview>
**Planning Tools** - For high-level goal documentation:
- \`createPlan\`: Create a strategic plan document. **Required params: goal, description (brief summary), context** - all three must be provided
- \`updatePlan\`: Update plan details (only planId is required)
**Todo Tools** - For actionable execution items:
- \`createTodos\`: Create new todo items from text array
- \`updateTodos\`: Batch update todos (add, update, remove, complete, processing operations)
- \`clearTodos\`: Clear completed or all items
**Todo Status Workflow:** todo → processing → completed (use "processing" when actively working on an item)
</tool_overview>
<default_workflow>
**CRITICAL: Most tasks do NOT need GTD tools. Only use them for complex, multi-step projects.**
**DO NOT use GTD tools for:**
- Simple one-step tasks (rename a file, send a message, search something)
- Quick questions or lookups
- Tasks that can be completed immediately with a single action
- Any request that doesn't require tracking progress over time
**ONLY use GTD tools when ALL of these are true:**
1. The task has multiple distinct steps that need tracking
2. The user explicitly wants to plan or organize something
3. Progress needs to be tracked over time (not completed in one response)
**When GTD tools ARE appropriate:**
1. **First**, use \`createPlan\` to document the goal and relevant context
2. **Then**, use \`createTodos\` to break down the plan into actionable steps
**Examples:**
- ❌ "Rename this file" → Just do it, no GTD needed
- ❌ "What's the weather?" → Just answer, no GTD needed
- ❌ "Help me write an email" → Just write it, no GTD needed
- ✅ "Help me plan a trip to Japan" → Use createPlan + createTodos
- ✅ "I want to learn Python, create a study plan" → Use createPlan + createTodos
- ✅ "Help me organize my project tasks" → Use createTodos (user explicitly wants organization)
</default_workflow>
<when_to_use>
**Use Plans when:**
- User explicitly asks to "plan", "organize", or "break down" a complex goal
- The project spans multiple sessions or days
- There's significant context, constraints, or background worth documenting
- The task has 5+ distinct steps that benefit from strategic organization
**Use Todos when:**
- Breaking down a plan into actionable steps (after creating a plan)
- User explicitly requests a checklist or task list
- Tracking progress on a multi-step project
**DO NOT use Plans/Todos when:**
- The task can be done in one action (rename, delete, send, search, etc.)
- The user just wants something done, not organized
- The task will be completed in this single conversation
- The user wants a task to repeat automatically on a schedule (daily/weekly/hourly) — use **lobe-cron** instead. Keywords like "daily task", "routine", "recurring", "every day/morning/week", "set as daily", "make it regular" all indicate scheduled automation, not GTD todo management.
</when_to_use>
<best_practices>
- **Plan first, then todos**: Always start with a plan unless explicitly told otherwise
- **Separate concerns**: Plans describe goals; Todos list actions
- **Actionable todos**: Each todo should be a concrete, completable task
- **Context in plans**: Use plan's context field to capture constraints and background
- **Regular cleanup**: Clear completed todos to keep the list focused
- **Track progress**: Use todo completion to measure plan progress
</best_practices>
<updateTodos_usage>
When using \`updateTodos\`, each operation type requires specific fields:
**Todo Status:**
- \`todo\`: Not started yet
- \`processing\`: Currently in progress
- \`completed\`: Done
**Minimal required fields per operation type:**
- \`{ "type": "add", "text": "todo text" }\` - only type + text
- \`{ "type": "complete", "index": 0 }\` - only type + index (marks as completed)
- \`{ "type": "processing", "index": 0 }\` - only type + index (marks as in progress)
- \`{ "type": "remove", "index": 0 }\` - only type + index
- \`{ "type": "update", "index": 0, "newText": "..." }\` - type + index + optional newText/status
**Example - mark item 0 as processing, item 1 as complete:**
\`\`\`json
{
"operations": [
{ "type": "processing", "index": 0 },
{ "type": "complete", "index": 1 }
]
}
\`\`\`
**DO NOT** add extra fields like \`"status": "completed"\` for complete/processing operations - they are ignored.
</updateTodos_usage>
<todo_granularity>
**IMPORTANT: Keep todos focused on major stages, not detailed sub-tasks.**
- **Limit to 5-10 items**: A todo list should contain around 5-10 major milestones or stages, not 20+ detailed tasks.
- **Think in phases**: Group related tasks into higher-level stages (e.g., "Plan itinerary" instead of listing every city separately).
- **Use hierarchical numbering** when more detail is needed: Use "1.", "2.", "2.1", "2.2", "3." format to show parent-child relationships.
**Good example** (Japan trip - 7 items, stage-focused):
- 1. Determine travel dates and duration
- 2. Handle visa and documentation
- 3. Book flights and accommodation
- 4. Plan city itineraries
- 5. Arrange local transportation
- 6. Prepare for departure
- 7. Final confirmation before trip
**Bad example** (20+ detailed items):
- Book Tokyo hotel
- Book Kyoto hotel
- Book Osaka hotel
- Buy Suica card
- Download Google Maps
- Download translation app
- ... (too granular!)
**When user needs more detail**, use hierarchical numbering:
- 1. Determine travel dates
- 2. Plan itinerary
- 2.1 Tokyo attractions (3 days)
- 2.2 Kyoto attractions (2 days)
- 2.3 Osaka attractions (2 days)
- 3. Handle bookings
- 3.1 Flights
- 3.2 Hotels
- 3.3 JR Pass
- 4. Departure preparation
</todo_granularity>
<plan_stability>
**IMPORTANT: Plans should remain stable once created. Each conversation has only ONE plan.**
- **Do NOT update plans** when details change (dates, locations, preferences). Instead, update the todos to reflect new information.
- **Only use updatePlan** when the user's goal fundamentally changes (e.g., destination changes from Japan to Korea).
- When user provides more specific information (like exact dates or preferences), **update or add todos** - not the plan.
Example:
- User: "Plan a trip to Japan" → Create plan with goal "Japan Trip"
- User: "I want to go in February" → Update todos to include February-specific tasks, NOT update the plan
- User: "Actually I want to go to Korea instead" → Use updatePlan to change the goal to "Korea Trip" (fundamental goal change)
</plan_stability>
<response_format>
When working with GTD tools:
- Confirm actions: "Created plan: [goal]" or "Added [n] todo items"
- Show progress: "Completed [n] items, [m] remaining"
- Be concise: Brief confirmations, not verbose explanations
- **NEVER repeat the todo list in your response** - Users can already see the todos in the UI component. Do not list or enumerate the todo items in your text output.
</response_format>`;
-222
View File
@@ -1,222 +0,0 @@
/**
* API names for GTD (Getting Things Done) tool
*
* GTD Tools help users and agents manage tasks effectively.
* These tools can be used by:
* - LobeAI default assistant for user task management
* - Group Supervisor for multi-agent task orchestration
*
* MVP version focuses on Plan and Todo functionality.
* Task management will be added in future iterations.
*/
export const GTDApiName = {
// ==================== Quick Todo ====================
/** Clear completed or all todos */
clearTodos: 'clearTodos',
// ==================== Planning ====================
/** Create a structured plan by breaking down a goal into actionable steps */
createPlan: 'createPlan',
/** Create new todo items */
createTodos: 'createTodos',
/** Update an existing plan */
updatePlan: 'updatePlan',
/** Update todo items with batch operations (add, update, remove, complete, processing) */
updateTodos: 'updateTodos',
} as const;
export type GTDApiNameType = (typeof GTDApiName)[keyof typeof GTDApiName];
// ==================== Todo Item ====================
/** Status of a todo item */
export type TodoStatus = 'todo' | 'processing' | 'completed';
export interface TodoItem {
/** Status of the todo item */
status: TodoStatus;
/** The todo item text */
text: string;
}
/** Get the next status in the cycle: todo → processing → completed → todo */
export const getNextTodoStatus = (current: TodoStatus): TodoStatus => {
const cycle: TodoStatus[] = ['todo', 'processing', 'completed'];
const index = cycle.indexOf(current);
return cycle[(index + 1) % cycle.length];
};
export interface TodoList {
items: TodoItem[];
updatedAt: string;
}
/** Alias for TodoList, used for state storage in Plan metadata */
export type TodoState = TodoList;
// ==================== Todo Params ====================
/**
* Create new todo items
* - AI input: { adds: string[] } - array of text strings from AI
* - After user edit: { items: TodoItem[] } - saved format with TodoItem objects
*/
export interface CreateTodosParams {
/** Array of text strings from AI */
adds?: string[];
/** Array of TodoItem objects (saved format after user edit) */
items?: TodoItem[];
}
/**
* Update operation types for batch updates
*/
export type TodoUpdateOperationType = 'add' | 'update' | 'remove' | 'complete' | 'processing';
/**
* Single update operation
*/
export interface TodoUpdateOperation {
/** For 'update', 'remove', 'complete', 'processing': the index of the item (0-based) */
index?: number;
/** For 'update': the new text */
newText?: string;
/** For 'update': the new status */
status?: TodoStatus;
/** For 'add': the text to add */
text?: string;
/** Operation type */
type: TodoUpdateOperationType;
}
/**
* Update todo list with batch operations
* Supports: add, update, remove, complete, processing
*/
export interface UpdateTodosParams {
/** Array of update operations to apply */
operations: TodoUpdateOperation[];
}
/**
* Clear todo items
*/
export interface ClearTodosParams {
/** Clear mode: 'completed' only clears done items, 'all' clears everything */
mode: 'completed' | 'all';
}
// ==================== Todo State Types for Render ====================
export interface CreateTodosState {
/** Items that were created */
createdItems: string[];
/** Current todo list after creation */
todos: TodoList;
}
export interface UpdateTodosState {
/** Operations that were applied */
appliedOperations: TodoUpdateOperation[];
/** Current todo list after update */
todos: TodoList;
}
export interface CompleteTodosState {
/** Indices that were completed */
completedIndices: number[];
/** Current todo list after completion */
todos: TodoList;
}
export interface RemoveTodosState {
/** Indices that were removed */
removedIndices: number[];
/** Current todo list after removal */
todos: TodoList;
}
export interface ClearTodosState {
/** Number of items cleared */
clearedCount: number;
/** Mode used for clearing */
mode: 'completed' | 'all';
/** Current todo list after clearing */
todos: TodoList;
}
// ==================== Planning Params ====================
/**
* Create a high-level plan document
* Plans define the strategic direction (what and why), not actionable steps
*
* Field mapping to Document:
* - goal -> document.title
* - description -> document.description
* - context -> document.content
*/
export interface CreatePlanParams {
/** Detailed context, background, constraints (maps to document.content) */
context?: string;
/** Brief summary of the plan (maps to document.description) */
description: string;
/** The main goal or objective to achieve (maps to document.title) */
goal: string;
}
export interface UpdatePlanParams {
/** Mark plan as completed */
completed?: boolean;
/** Updated context (maps to document.content) */
context?: string;
/** Updated description (maps to document.description) */
description?: string;
/** Updated goal (maps to document.title) */
goal?: string;
/** Plan ID to update */
planId: string;
}
// ==================== Plan Result Types ====================
/**
* A high-level plan document
* Contains goal and context, but no steps (steps are managed via Todos)
*
* Field mapping to Document:
* - goal -> document.title
* - description -> document.description
* - context -> document.content
*/
export interface Plan {
/** Whether the plan is completed */
completed: boolean;
/** Detailed context, background, constraints (maps to document.content) */
context?: string;
/** Creation timestamp */
createdAt: string;
/** Brief summary of the plan (maps to document.description) */
description: string;
/** The main goal or objective (maps to document.title) */
goal: string;
/** Unique plan identifier */
id: string;
/** Last update timestamp */
updatedAt: string;
}
// ==================== Plan State Types for Render ====================
export interface CreatePlanState {
/** The created plan document */
plan: Plan;
}
export interface UpdatePlanState {
/** The updated plan document */
plan: Plan;
}
@@ -4,7 +4,6 @@
"private": true,
"exports": {
".": "./src/index.ts",
"./executor": "./src/executor/index.ts",
"./client": "./src/client/index.ts"
},
"main": "./src/index.ts",
@@ -14,13 +13,16 @@
"test:update": "vitest -u"
},
"dependencies": {
"@lobechat/const": "workspace:*"
"@lobechat/const": "workspace:*",
"@lobechat/prompts": "workspace:*"
},
"devDependencies": {
"@lobechat/types": "workspace:*"
},
"peerDependencies": {
"@lobehub/editor": "^4",
"@lobehub/ui": "^5",
"antd": "^6",
"antd-style": "*",
"lucide-react": "*",
"react": "*"
@@ -26,15 +26,15 @@ export const ClearTodosInspector = memo<BuiltinInspectorProps<ClearTodosParams,
if (isArgumentsStreaming && !mode) {
return (
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-gtd.apiName.clearTodos')}</span>
<span>{t('builtins.lobe-agent.apiName.clearTodos')}</span>
</div>
);
}
const modeLabel =
mode === 'all'
? t('builtins.lobe-gtd.apiName.clearTodos.modeAll')
: t('builtins.lobe-gtd.apiName.clearTodos.modeCompleted');
? t('builtins.lobe-agent.apiName.clearTodos.modeAll')
: t('builtins.lobe-agent.apiName.clearTodos.modeCompleted');
return (
<div
@@ -42,7 +42,7 @@ export const ClearTodosInspector = memo<BuiltinInspectorProps<ClearTodosParams,
>
<Trans
components={{ mode: <span className={styles.mode} /> }}
i18nKey="builtins.lobe-gtd.apiName.clearTodos.result"
i18nKey="builtins.lobe-agent.apiName.clearTodos.result"
ns="plugin"
values={{ mode: modeLabel }}
/>
@@ -18,7 +18,7 @@ export const CreatePlanInspector = memo<BuiltinInspectorProps<CreatePlanParams,
if (isArgumentsStreaming && !goal) {
return (
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-gtd.apiName.createPlan')}</span>
<span>{t('builtins.lobe-agent.apiName.createPlan')}</span>
</div>
);
}
@@ -30,12 +30,12 @@ export const CreatePlanInspector = memo<BuiltinInspectorProps<CreatePlanParams,
{goal ? (
<Trans
components={{ goal: <span className={highlightTextStyles.primary} /> }}
i18nKey="builtins.lobe-gtd.apiName.createPlan.result"
i18nKey="builtins.lobe-agent.apiName.createPlan.result"
ns="plugin"
values={{ goal }}
/>
) : (
<span>{t('builtins.lobe-gtd.apiName.createPlan')}</span>
<span>{t('builtins.lobe-agent.apiName.createPlan')}</span>
)}
</div>
);
@@ -30,14 +30,14 @@ export const CreateTodosInspector = memo<
if (isArgumentsStreaming && count === 0) {
return (
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-gtd.apiName.createTodos')}</span>
<span>{t('builtins.lobe-agent.apiName.createTodos')}</span>
</div>
);
}
return (
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.createTodos')}</span>
<span className={styles.title}>{t('builtins.lobe-agent.apiName.createTodos')}</span>
{count > 0 && (
<Text code as={'span'} color={cssVar.colorSuccess} fontSize={12}>
<Icon icon={Plus} size={12} />
@@ -29,24 +29,24 @@ export const UpdatePlanInspector = memo<BuiltinInspectorProps<UpdatePlanParams,
if (isArgumentsStreaming && !planId) {
return (
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-gtd.apiName.updatePlan')}</span>
<span>{t('builtins.lobe-agent.apiName.updatePlan')}</span>
</div>
);
}
return (
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.updatePlan')}</span>
<span className={styles.title}>{t('builtins.lobe-agent.apiName.updatePlan')}</span>
{completed && (
<Text code as={'span'} color={cssVar.colorSuccess} fontSize={12}>
<Icon icon={CheckCircle} size={12} />
{t('builtins.lobe-gtd.apiName.updatePlan.completed')}
{t('builtins.lobe-agent.apiName.updatePlan.completed')}
</Text>
)}
{hasUpdates && !completed && (
<Text code as={'span'} color={cssVar.colorWarning} fontSize={12}>
<Icon icon={DiffIcon} size={12} />
{t('builtins.lobe-gtd.apiName.updatePlan.modified')}
{t('builtins.lobe-agent.apiName.updatePlan.modified')}
</Text>
)}
</div>
@@ -62,7 +62,7 @@ export const UpdateTodosInspector = memo<
if (isArgumentsStreaming && !hasOperations) {
return (
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
<span>{t('builtins.lobe-gtd.apiName.updateTodos')}</span>
<span>{t('builtins.lobe-agent.apiName.updateTodos')}</span>
</div>
);
}
@@ -103,7 +103,7 @@ export const UpdateTodosInspector = memo<
return (
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.updateTodos')}</span>
<span className={styles.title}>{t('builtins.lobe-agent.apiName.updateTodos')}</span>
{statsParts.length > 0 && (
<>
{statsParts.map((part, index) => (
@@ -3,6 +3,11 @@ import type { BuiltinInspector } from '@lobechat/types';
import { LobeAgentApiName } from '../../types';
import { CallSubAgentInspector } from './CallSubAgent';
import { CallSubAgentsInspector } from './CallSubAgents';
import { ClearTodosInspector } from './ClearTodos';
import { CreatePlanInspector } from './CreatePlan';
import { CreateTodosInspector } from './CreateTodos';
import { UpdatePlanInspector } from './UpdatePlan';
import { UpdateTodosInspector } from './UpdateTodos';
/**
* Lobe Agent Inspector Components Registry
@@ -13,4 +18,9 @@ import { CallSubAgentsInspector } from './CallSubAgents';
export const LobeAgentInspectors: Record<string, BuiltinInspector> = {
[LobeAgentApiName.callSubAgent]: CallSubAgentInspector as BuiltinInspector,
[LobeAgentApiName.callSubAgents]: CallSubAgentsInspector as BuiltinInspector,
[LobeAgentApiName.clearTodos]: ClearTodosInspector as BuiltinInspector,
[LobeAgentApiName.createPlan]: CreatePlanInspector as BuiltinInspector,
[LobeAgentApiName.createTodos]: CreateTodosInspector as BuiltinInspector,
[LobeAgentApiName.updatePlan]: UpdatePlanInspector as BuiltinInspector,
[LobeAgentApiName.updateTodos]: UpdateTodosInspector as BuiltinInspector,
};
@@ -21,9 +21,9 @@ const AddTodoIntervention = memo<BuiltinInterventionProps<CreateTodosParams>>(
const handleSave = useCallback(
async (items: TodoItem[]) => {
console.log('[AddTodoIntervention] handleSave called with', items.length, 'items');
console.info('[AddTodoIntervention] handleSave called with', items.length, 'items');
await onArgsChange?.({ items });
console.log('[AddTodoIntervention] onArgsChange completed');
console.info('[AddTodoIntervention] onArgsChange completed');
},
[onArgsChange],
);
@@ -32,7 +32,7 @@ const AddTodoIntervention = memo<BuiltinInterventionProps<CreateTodosParams>>(
<Block variant={'outlined'}>
<SortableTodoList
defaultItems={defaultItems}
placeholder={t('lobe-gtd.addTodo.placeholder')}
placeholder={t('lobe-agent.addTodo.placeholder')}
registerBeforeApprove={registerBeforeApprove}
onSave={handleSave}
/>
@@ -55,20 +55,20 @@ const ClearTodosIntervention = memo<BuiltinInterventionProps<ClearTodosParams>>(
<Flexbox gap={12}>
<Flexbox horizontal align="center" className={styles.header} gap={8}>
<Trash2 size={16} />
<span style={{ fontSize: 14, fontWeight: 500 }}>{t('lobe-gtd.clearTodos.header')}</span>
<span style={{ fontSize: 14, fontWeight: 500 }}>{t('lobe-agent.clearTodos.header')}</span>
</Flexbox>
<Flexbox className={styles.container} gap={8}>
<span className={styles.label}>{t('lobe-gtd.clearTodos.label')}</span>
<span className={styles.label}>{t('lobe-agent.clearTodos.label')}</span>
<Radio.Group value={mode} onChange={handleModeChange}>
<Flexbox gap={8}>
<Radio value="completed">
<span className={styles.normalText}>
{t('lobe-gtd.clearTodos.option.completed')}
{t('lobe-agent.clearTodos.option.completed')}
</span>
</Radio>
<Radio value="all">
<span className={styles.dangerText}>{t('lobe-gtd.clearTodos.option.all')}</span>
<span className={styles.dangerText}>{t('lobe-agent.clearTodos.option.all')}</span>
</Radio>
</Flexbox>
</Radio.Group>
@@ -143,7 +143,7 @@ const CreatePlanIntervention = memo<BuiltinInterventionProps<CreatePlanParams>>(
<TextArea
autoSize={{ minRows: 1 }}
className={styles.title}
placeholder={t('lobe-gtd.createPlan.goal.placeholder')}
placeholder={t('lobe-agent.createPlan.goal.placeholder')}
style={{ padding: 0, resize: 'none' }}
value={goal}
variant={'borderless'}
@@ -156,7 +156,7 @@ const CreatePlanIntervention = memo<BuiltinInterventionProps<CreatePlanParams>>(
autoSize={{ minRows: 1 }}
className={styles.description}
data-testid="plan-description"
placeholder={t('lobe-gtd.createPlan.description.placeholder')}
placeholder={t('lobe-agent.createPlan.description.placeholder')}
style={{ padding: 0, resize: 'none' }}
value={description}
variant={'borderless'}
@@ -169,8 +169,8 @@ const CreatePlanIntervention = memo<BuiltinInterventionProps<CreatePlanParams>>(
<Editor
content={args.context}
editor={editor}
lineEmptyPlaceholder={t('lobe-gtd.createPlan.context.placeholder')}
placeholder={t('lobe-gtd.createPlan.context.placeholder')}
lineEmptyPlaceholder={t('lobe-agent.createPlan.context.placeholder')}
placeholder={t('lobe-agent.createPlan.context.placeholder')}
type={'text'}
plugins={[
ReactListPlugin,
@@ -1,20 +1,20 @@
import type { BuiltinIntervention } from '@lobechat/types';
import { GTDApiName } from '../../types';
import { LobeAgentApiName } from '../../types';
import AddTodoIntervention from './AddTodo';
import ClearTodosIntervention from './ClearTodos';
import CreatePlanIntervention from './CreatePlan';
/**
* GTD Tool Intervention Components Registry
* Lobe Agent Intervention Components Registry
*
* Intervention components allow users to review and modify tool parameters
* before the tool is executed.
*/
export const GTDInterventions: Record<string, BuiltinIntervention> = {
[GTDApiName.clearTodos]: ClearTodosIntervention as BuiltinIntervention,
[GTDApiName.createPlan]: CreatePlanIntervention as BuiltinIntervention,
[GTDApiName.createTodos]: AddTodoIntervention as BuiltinIntervention,
export const LobeAgentInterventions: Record<string, BuiltinIntervention> = {
[LobeAgentApiName.clearTodos]: ClearTodosIntervention as BuiltinIntervention,
[LobeAgentApiName.createPlan]: CreatePlanIntervention as BuiltinIntervention,
[LobeAgentApiName.createTodos]: AddTodoIntervention as BuiltinIntervention,
};
export { default as AddTodoIntervention } from './AddTodo';
@@ -1,18 +1,32 @@
import { LobeAgentApiName } from '../../types';
import CallSubAgentRender from './CallSubAgent';
import CallSubAgentsRender from './CallSubAgents';
import CreatePlan from './CreatePlan';
import TodoListRender from './TodoList';
/**
* Lobe Agent Tool Render Components Registry
*
* Sub-agent dispatch operations render a card showing the dispatched
* task(s). The `analyzeVisualMedia` API has no dedicated render — its
* textual answer is rendered by the default tool-result UI.
* task(s). Plan operations render the PlanCard UI. Todo operations
* share a single TodoList render.
*/
export const LobeAgentRenders = {
[LobeAgentApiName.callSubAgent]: CallSubAgentRender,
[LobeAgentApiName.callSubAgents]: CallSubAgentsRender,
// Plan operations render the PlanCard UI
[LobeAgentApiName.createPlan]: CreatePlan,
[LobeAgentApiName.updatePlan]: CreatePlan,
// All todo operations render the same TodoList UI
[LobeAgentApiName.clearTodos]: TodoListRender,
[LobeAgentApiName.createTodos]: TodoListRender,
[LobeAgentApiName.updateTodos]: TodoListRender,
};
export { default as CallSubAgentRender } from './CallSubAgent';
export { default as CallSubAgentsRender } from './CallSubAgents';
export { default as CreatePlan, PlanCard } from './CreatePlan';
export type { TodoListRenderState } from './TodoList';
export { default as TodoListRender, TodoListUI } from './TodoList';
@@ -3,6 +3,7 @@ import type { BuiltinStreaming } from '@lobechat/types';
import { LobeAgentApiName } from '../../types';
import { CallSubAgentStreaming } from './CallSubAgent';
import { CallSubAgentsStreaming } from './CallSubAgents';
import { CreatePlanStreaming } from './CreatePlan';
/**
* Lobe Agent Streaming Components Registry
@@ -13,7 +14,9 @@ import { CallSubAgentsStreaming } from './CallSubAgents';
export const LobeAgentStreamings: Record<string, BuiltinStreaming> = {
[LobeAgentApiName.callSubAgent]: CallSubAgentStreaming as BuiltinStreaming,
[LobeAgentApiName.callSubAgents]: CallSubAgentsStreaming as BuiltinStreaming,
[LobeAgentApiName.createPlan]: CreatePlanStreaming as BuiltinStreaming,
};
export { CallSubAgentStreaming } from './CallSubAgent';
export { CallSubAgentsStreaming } from './CallSubAgents';
export { CreatePlanStreaming } from './CreatePlan';
@@ -29,7 +29,7 @@ interface AddItemRowProps {
const AddItemRow = memo<AddItemRowProps>(({ placeholder, showDragHandle = true, className }) => {
const { t } = useTranslation('tool');
const inputRef = useRef<InputRef>(null);
const defaultPlaceholder = placeholder || t('lobe-gtd.addTodo.placeholder');
const defaultPlaceholder = placeholder || t('lobe-agent.addTodo.placeholder');
const newItemText = useTodoListStore((s) => s.newItemText);
const focusedId = useTodoListStore((s) => s.focusedId);
@@ -53,7 +53,7 @@ interface TodoItemRowProps {
const TodoItemRow = memo<TodoItemRowProps>(({ id, placeholder }) => {
const { t } = useTranslation('tool');
const inputRef = useRef<InputRef>(null);
const defaultPlaceholder = placeholder || t('lobe-gtd.todoItem.placeholder');
const defaultPlaceholder = placeholder || t('lobe-agent.todoItem.placeholder');
// Find item by stable id
const item = useTodoListStore((s) => s.items.find((item) => item.id === id));
@@ -42,9 +42,9 @@ const SortableTodoList = memo<SortableTodoListProps>(
useEffect(() => {
if (registerBeforeApprove) {
const unregister = registerBeforeApprove('sortable-todo-list', async () => {
console.log('trigger save');
console.info('trigger save');
await store.getState().saveNow();
console.log('trigger save successful');
console.info('trigger save successful');
});
// Cleanup: unregister on unmount
return unregister;
@@ -27,11 +27,11 @@ export const createActions = (
): TodoListStore => {
// Save implementation (used by both debounced and immediate save)
const performSave = async () => {
console.log('[performSave] called, onSave:', !!internals.onSave);
console.info('[performSave] called, onSave:', !!internals.onSave);
if (!internals.onSave) return;
const { items, isDirty } = get();
console.log('[performSave] isDirty:', isDirty, 'items:', items.length);
console.info('[performSave] isDirty:', isDirty, 'items:', items.length);
if (!isDirty) return;
set({ saveStatus: 'saving' });
@@ -39,9 +39,9 @@ export const createActions = (
try {
// Convert TodoListItem[] to TodoItem[] (remove id)
const todoItems: TodoItem[] = items.map(({ status, text }) => ({ status, text }));
console.log('[performSave] calling onSave with', todoItems.length, 'items');
console.info('[performSave] calling onSave with', todoItems.length, 'items');
await internals.onSave(todoItems);
console.log('[performSave] onSave completed');
console.info('[performSave] onSave completed');
set({ isDirty: false, saveStatus: 'saved' });
} catch {
set({ saveStatus: 'error' });
@@ -98,9 +98,9 @@ export const createActions = (
try {
const todoItems: TodoItem[] = items.map(({ status, text }) => ({ status, text }));
console.log('[saveNow] force saving', todoItems.length, 'items');
console.info('[saveNow] force saving', todoItems.length, 'items');
await internals.onSave(todoItems);
console.log('[saveNow] save completed');
console.info('[saveNow] save completed');
set({ isDirty: false, saveStatus: 'saved' });
setTimeout(() => {
@@ -10,7 +10,7 @@ import type {
TodoState,
UpdatePlanParams,
UpdateTodosParams,
} from '../types';
} from '../../../types';
export interface PlanDocument {
content: string | null;
@@ -22,7 +22,7 @@ export interface PlanDocument {
updatedAt: Date;
}
export interface GTDRuntimeService {
export interface PlanRuntimeService {
createPlan: (args: {
content: string;
description: string;
@@ -48,7 +48,7 @@ export interface GTDRuntimeService {
updatePlanMetadata: (id: string, metadata: Record<string, any>) => Promise<void>;
}
export interface GTDRuntimeContext {
export interface PlanRuntimeContext {
/**
* Existing todos supplied by the caller (client: from stepContext / pluginState).
* When undefined, the runtime resolves todos from the plan document's metadata.
@@ -81,14 +81,14 @@ const readTodosFromPlan = (doc: PlanDocument | null): TodoItem[] => {
return [];
};
export class GTDExecutionRuntime {
private service: GTDRuntimeService;
export class PlanExecutionRuntime {
private service: PlanRuntimeService;
constructor(service: GTDRuntimeService) {
constructor(service: PlanRuntimeService) {
this.service = service;
}
private async resolveExistingTodos(context: GTDRuntimeContext): Promise<TodoItem[]> {
private async resolveExistingTodos(context: PlanRuntimeContext): Promise<TodoItem[]> {
if (context.currentTodos) return context.currentTodos;
if (!context.topicId) return [];
const plan = await this.service.findPlanByTopic(context.topicId);
@@ -109,7 +109,7 @@ export class GTDExecutionRuntime {
createTodos = async (
params: CreateTodosParams,
context: GTDRuntimeContext,
context: PlanRuntimeContext,
): Promise<BuiltinToolResult> => {
const itemsToAdd: TodoItem[] = params.items
? params.items
@@ -144,7 +144,7 @@ export class GTDExecutionRuntime {
updateTodos = async (
params: UpdateTodosParams,
context: GTDRuntimeContext,
context: PlanRuntimeContext,
): Promise<BuiltinToolResult> => {
const { operations } = params;
@@ -218,7 +218,7 @@ export class GTDExecutionRuntime {
clearTodos = async (
params: ClearTodosParams,
context: GTDRuntimeContext,
context: PlanRuntimeContext,
): Promise<BuiltinToolResult> => {
const { mode } = params;
@@ -270,7 +270,7 @@ export class GTDExecutionRuntime {
createPlan = async (
params: CreatePlanParams,
context: GTDRuntimeContext,
context: PlanRuntimeContext,
): Promise<BuiltinToolResult> => {
try {
if (context.signal?.aborted) return { stop: true, success: false };
@@ -307,7 +307,7 @@ export class GTDExecutionRuntime {
updatePlan = async (
params: UpdatePlanParams,
context: GTDRuntimeContext,
context: PlanRuntimeContext,
): Promise<BuiltinToolResult> => {
try {
if (context.signal?.aborted) return { stop: true, success: false };
@@ -1,10 +1,22 @@
import type { BuiltinToolContext, BuiltinToolResult, ChatStreamPayload } from '@lobechat/types';
import { BaseExecutor, RequestTrigger } from '@lobechat/types';
import { LobeAgentManifest } from '../manifest';
import type { AnalyzeVisualMediaParams, CallSubAgentParams, CallSubAgentsParams } from '../types';
import { LobeAgentApiName } from '../types';
import type { VisualFileItem } from '../visualMedia';
import { notebookService } from '@/services/notebook';
import { useNotebookStore } from '@/store/notebook';
import { LobeAgentManifest } from '../../manifest';
import type {
AnalyzeVisualMediaParams,
CallSubAgentParams,
CallSubAgentsParams,
ClearTodosParams,
CreatePlanParams,
CreateTodosParams,
UpdatePlanParams,
UpdateTodosParams,
} from '../../types';
import { LobeAgentApiName } from '../../types';
import type { VisualFileItem } from '../../visualMedia';
import {
buildAnalyzeVisualMediaContent,
createUrlVisualFileItems,
@@ -15,7 +27,86 @@ import {
normalizeAnalyzeVisualMediaInput,
selectVisualFileItems,
validateVisualMediaUrls,
} from '../visualMedia';
} from '../../visualMedia';
import {
type PlanDocument,
PlanExecutionRuntime,
type PlanRuntimeContext,
type PlanRuntimeService,
} from './PlanRuntime';
import { getTodosFromContext } from './planTodoHelper';
const PLAN_DOC_TYPE = 'agent/plan';
/**
* Normalize a document payload returned by notebookService / useNotebookStore
* into the `PlanDocument` shape expected by PlanExecutionRuntime.
*/
const normalizePlanDoc = (doc: {
content?: string | null;
createdAt: Date | string;
description?: string | null;
id: string;
metadata?: Record<string, any> | null;
title?: string | null;
updatedAt: Date | string;
}): PlanDocument => ({
content: doc.content ?? null,
createdAt: typeof doc.createdAt === 'string' ? new Date(doc.createdAt) : doc.createdAt,
description: doc.description ?? null,
id: doc.id,
metadata: doc.metadata ?? null,
title: doc.title ?? null,
updatedAt: typeof doc.updatedAt === 'string' ? new Date(doc.updatedAt) : doc.updatedAt,
});
/**
* Client-side implementation of the Plan runtime service.
* Routes user-facing plan CRUD through useNotebookStore (so SWR caches refresh),
* and keeps silent metadata writes (todos sync) on the raw notebookService.
*/
const clientPlanService: PlanRuntimeService = {
createPlan: async ({ topicId, goal, description, content }) => {
const doc = await useNotebookStore.getState().createDocument({
content,
description,
title: goal,
topicId,
type: PLAN_DOC_TYPE,
});
return normalizePlanDoc(doc);
},
findPlanById: async (id) => {
const doc = await notebookService.getDocument(id);
return doc ? normalizePlanDoc(doc) : null;
},
findPlanByTopic: async (topicId) => {
const result = await notebookService.listDocuments({ topicId, type: PLAN_DOC_TYPE });
const first = result.data[0];
return first ? normalizePlanDoc(first) : null;
},
updatePlan: async (id, { goal, description, content }, topicId) => {
const doc = await useNotebookStore
.getState()
.updateDocument({ content, description, id, title: goal }, topicId ?? '');
if (!doc) throw new Error(`Plan not found after update: ${id}`);
return normalizePlanDoc(doc);
},
updatePlanMetadata: async (id, metadata) => {
await notebookService.updateDocument({ id, metadata });
},
};
const toPlanRuntimeContext = (ctx: BuiltinToolContext): PlanRuntimeContext => ({
currentTodos: getTodosFromContext(ctx),
messageId: ctx.messageId,
signal: ctx.signal,
topicId: ctx.topicId ?? undefined,
});
interface VisualSourceMessage {
parentId?: string;
@@ -50,6 +141,27 @@ class LobeAgentExecutor extends BaseExecutor<typeof LobeAgentApiName> {
readonly identifier = LobeAgentManifest.identifier;
protected readonly apiEnum = LobeAgentApiName;
private planRuntime = new PlanExecutionRuntime(clientPlanService);
// ==================== Plan / Todo ====================
createPlan = (params: CreatePlanParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.planRuntime.createPlan(params, toPlanRuntimeContext(ctx));
updatePlan = (params: UpdatePlanParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.planRuntime.updatePlan(params, toPlanRuntimeContext(ctx));
createTodos = (params: CreateTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.planRuntime.createTodos(params, toPlanRuntimeContext(ctx));
updateTodos = (params: UpdateTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.planRuntime.updateTodos(params, toPlanRuntimeContext(ctx));
clearTodos = (params: ClearTodosParams, ctx: BuiltinToolContext): Promise<BuiltinToolResult> =>
this.planRuntime.clearTodos(params, toPlanRuntimeContext(ctx));
// ==================== Visual ====================
analyzeVisualMedia = async (
params: AnalyzeVisualMediaParams,
ctx: BuiltinToolContext,
@@ -1,6 +1,6 @@
import type { BuiltinToolContext } from '@lobechat/types';
import type { TodoItem } from '../types';
import type { TodoItem } from '../../types';
/**
* Helper to get todos from step context or fallback to plugin state
@@ -1,11 +1,40 @@
// Executor (client-side — depends on app stores/services)
export { lobeAgentExecutor } from './executor';
// Inspector components (customized tool call headers)
export { LobeAgentInspectors } from './Inspector';
// Render components (read-only snapshots)
export { CallSubAgentRender, CallSubAgentsRender, LobeAgentRenders } from './Render';
export type { TodoListRenderState } from './Render';
export {
CallSubAgentRender,
CallSubAgentsRender,
CreatePlan,
LobeAgentRenders,
PlanCard,
TodoListRender,
TodoListUI,
} from './Render';
// Streaming components (real-time tool execution feedback)
export { CallSubAgentsStreaming, CallSubAgentStreaming, LobeAgentStreamings } from './Streaming';
export {
CallSubAgentsStreaming,
CallSubAgentStreaming,
CreatePlanStreaming,
LobeAgentStreamings,
} from './Streaming';
// Intervention components (interactive editing)
export {
AddTodoIntervention,
ClearTodosIntervention,
CreatePlanIntervention,
LobeAgentInterventions,
} from './Intervention';
// Reusable components
export type { SortableTodoListProps, TodoListItem } from './components';
export { SortableTodoList } from './components';
// Re-export types and manifest for convenience
export { LobeAgentManifest } from '../manifest';
@@ -1,3 +1,10 @@
// Plan execution runtime — pure logic, safe to consume server-side
export {
type PlanDocument,
PlanExecutionRuntime,
type PlanRuntimeContext,
type PlanRuntimeService,
} from './client/executor/PlanRuntime';
export * from './manifest';
export * from './systemRole';
export * from './types';
@@ -6,7 +6,7 @@ describe('LobeAgentManifest', () => {
it('should keep the package metadata generic for future Lobe Agent capabilities', () => {
expect(LobeAgentManifest.meta.avatar).toBe('🤖');
expect(LobeAgentManifest.meta.description).toBe(
'Run built-in Lobe Agent capabilities, including dispatching sub-agents.',
'Run built-in Lobe Agent capabilities: plan + todo management, sub-agent dispatch, and visual media analysis.',
);
expect(LobeAgentManifest.meta.readme).toContain(
'built-in assistant capabilities that can be expanded over time',
@@ -40,6 +40,149 @@ export const LobeAgentManifest: BuiltinToolManifest = {
},
},
// ==================== Planning ====================
{
description:
'Create a high-level plan document. Plans define the strategic direction (the "what" and "why"), while todos handle the actionable steps.',
name: LobeAgentApiName.createPlan,
humanIntervention: 'required',
renderDisplayControl: 'expand',
parameters: {
properties: {
goal: {
description: 'The main goal or objective to achieve (used as document title).',
type: 'string',
},
description: {
description: 'A brief summary of the plan (1-2 sentences).',
type: 'string',
},
context: {
description:
'Detailed context, constraints, background information, or strategic considerations relevant to the goal.',
type: 'string',
},
},
required: ['goal', 'description', 'context'],
type: 'object',
},
},
{
description:
'Update an existing plan document. Only use this when the goal fundamentally changes. Plans should remain stable once created - do not update plans just because details change.',
name: LobeAgentApiName.updatePlan,
parameters: {
properties: {
planId: {
description:
'The document ID of the plan to update (e.g., "docs_xxx"). This ID is returned in the createPlan response. Do NOT use the goal text as planId.',
type: 'string',
},
goal: {
description: 'Updated goal (document title).',
type: 'string',
},
description: {
description: 'Updated brief summary.',
type: 'string',
},
context: {
description: 'Updated detailed context.',
type: 'string',
},
},
required: ['planId'],
type: 'object',
},
},
// ==================== Quick Todo ====================
{
description: 'Create new todo items. Pass an array of text strings.',
name: LobeAgentApiName.createTodos,
humanIntervention: 'required',
parameters: {
properties: {
adds: {
description: 'Array of todo item texts to create.',
items: { type: 'string' },
type: 'array',
},
},
required: ['adds'],
type: 'object',
},
},
{
description: `Update todo items with batch operations. Each operation type requires specific fields:
- "add": requires "text" (the todo text to add)
- "update": requires "index", optional "newText" and/or "status"
- "remove": requires "index" only
- "complete": requires "index" only (marks item as completed)
- "processing": requires "index" only (marks item as in progress)`,
name: LobeAgentApiName.updateTodos,
renderDisplayControl: 'expand',
parameters: {
properties: {
operations: {
description:
'Array of update operations. IMPORTANT: For "complete", "processing" and "remove" operations, only pass "type" and "index" - no other fields needed.',
items: {
properties: {
type: {
description:
'Operation type. "add" needs text, "update" needs index + optional newText/status, "remove", "complete" and "processing" need index only.',
enum: ['add', 'update', 'remove', 'complete', 'processing'],
type: 'string',
},
text: {
description: 'Required for "add" only: the text to add.',
type: 'string',
},
index: {
description:
'Required for "update", "remove", "complete", "processing": the item index (0-based).',
type: 'number',
},
newText: {
description: 'Optional for "update" only: the new text.',
type: 'string',
},
status: {
description:
'Optional for "update" only: set status (todo, processing, completed).',
enum: ['todo', 'processing', 'completed'],
type: 'string',
},
},
required: ['type'],
type: 'object',
},
type: 'array',
},
},
required: ['operations'],
type: 'object',
},
},
{
description: 'Clear todo items. Can clear only completed items or all items.',
name: LobeAgentApiName.clearTodos,
humanIntervention: 'always',
renderDisplayControl: 'expand',
parameters: {
properties: {
mode: {
description: '"completed" clears only done items, "all" clears the entire list.',
enum: ['completed', 'all'],
type: 'string',
},
},
required: ['mode'],
type: 'object',
},
},
// ==================== Sub-Agent ====================
{
description:
@@ -125,7 +268,8 @@ export const LobeAgentManifest: BuiltinToolManifest = {
identifier: LobeAgentIdentifier,
meta: {
avatar: '🤖',
description: 'Run built-in Lobe Agent capabilities, including dispatching sub-agents.',
description:
'Run built-in Lobe Agent capabilities: plan + todo management, sub-agent dispatch, and visual media analysis.',
readme: 'Lobe Agent provides built-in assistant capabilities that can be expanded over time.',
title: 'Lobe Agent',
},
@@ -60,5 +60,151 @@ Use \`callSubAgent\` for a single sub-agent, \`callSubAgents\` for multiple para
</sub_agents>
${isDesktop ? runInClientSection : ''}`;
const planTodoSection = `
<plan_and_todos>
You have **plan and todo management** tools to organize multi-step work over time.
- **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.
- **Todo**: The concrete execution list with actionable items. Todos define the "how" - specific tasks to accomplish the plan. **Todos are dynamic** - they can be added, updated, completed, and removed as work progresses.
**Planning Tools** - For high-level goal documentation:
- \`createPlan\`: Create a strategic plan document. **Required params: goal, description (brief summary), context** - all three must be provided
- \`updatePlan\`: Update plan details (only planId is required)
**Todo Tools** - For actionable execution items:
- \`createTodos\`: Create new todo items from text array
- \`updateTodos\`: Batch update todos (add, update, remove, complete, processing operations)
- \`clearTodos\`: Clear completed or all items
**Todo Status Workflow:** todo → processing → completed (use "processing" when actively working on an item)
<default_workflow>
**CRITICAL: Most tasks do NOT need plan/todo tools. Only use them for complex, multi-step projects.**
**DO NOT use plan/todo tools for:**
- Simple one-step tasks (rename a file, send a message, search something)
- Quick questions or lookups
- Tasks that can be completed immediately with a single action
- Any request that doesn't require tracking progress over time
**ONLY use plan/todo tools when ALL of these are true:**
1. The task has multiple distinct steps that need tracking
2. The user explicitly wants to plan or organize something
3. Progress needs to be tracked over time (not completed in one response)
**When plan/todo tools ARE appropriate:**
1. **First**, use \`createPlan\` to document the goal and relevant context
2. **Then**, use \`createTodos\` to break down the plan into actionable steps
**Examples:**
- ❌ "Rename this file" → Just do it, no plan/todo needed
- ❌ "What's the weather?" → Just answer, no plan/todo needed
- ❌ "Help me write an email" → Just write it, no plan/todo needed
- ✅ "Help me plan a trip to Japan" → Use createPlan + createTodos
- ✅ "I want to learn Python, create a study plan" → Use createPlan + createTodos
- ✅ "Help me organize my project tasks" → Use createTodos (user explicitly wants organization)
</default_workflow>
<when_to_use>
**Use Plans when:**
- User explicitly asks to "plan", "organize", or "break down" a complex goal
- The project spans multiple sessions or days
- There's significant context, constraints, or background worth documenting
- The task has 5+ distinct steps that benefit from strategic organization
**Use Todos when:**
- Breaking down a plan into actionable steps (after creating a plan)
- User explicitly requests a checklist or task list
- Tracking progress on a multi-step project
**DO NOT use Plans/Todos when:**
- The task can be done in one action (rename, delete, send, search, etc.)
- The user just wants something done, not organized
- The task will be completed in this single conversation
- The user wants a task to repeat automatically on a schedule (daily/weekly/hourly) — use **lobe-cron** instead. Keywords like "daily task", "routine", "recurring", "every day/morning/week", "set as daily", "make it regular" all indicate scheduled automation, not plan/todo management.
</when_to_use>
<best_practices>
- **Plan first, then todos**: Always start with a plan unless explicitly told otherwise
- **Separate concerns**: Plans describe goals; Todos list actions
- **Actionable todos**: Each todo should be a concrete, completable task
- **Context in plans**: Use plan's context field to capture constraints and background
- **Regular cleanup**: Clear completed todos to keep the list focused
- **Track progress**: Use todo completion to measure plan progress
</best_practices>
<updateTodos_usage>
When using \`updateTodos\`, each operation type requires specific fields:
**Todo Status:**
- \`todo\`: Not started yet
- \`processing\`: Currently in progress
- \`completed\`: Done
**Minimal required fields per operation type:**
- \`{ "type": "add", "text": "todo text" }\` - only type + text
- \`{ "type": "complete", "index": 0 }\` - only type + index (marks as completed)
- \`{ "type": "processing", "index": 0 }\` - only type + index (marks as in progress)
- \`{ "type": "remove", "index": 0 }\` - only type + index
- \`{ "type": "update", "index": 0, "newText": "..." }\` - type + index + optional newText/status
**Example - mark item 0 as processing, item 1 as complete:**
\`\`\`json
{
"operations": [
{ "type": "processing", "index": 0 },
{ "type": "complete", "index": 1 }
]
}
\`\`\`
**DO NOT** add extra fields like \`"status": "completed"\` for complete/processing operations - they are ignored.
</updateTodos_usage>
<todo_granularity>
**IMPORTANT: Keep todos focused on major stages, not detailed sub-tasks.**
- **Limit to 5-10 items**: A todo list should contain around 5-10 major milestones or stages, not 20+ detailed tasks.
- **Think in phases**: Group related tasks into higher-level stages (e.g., "Plan itinerary" instead of listing every city separately).
- **Use hierarchical numbering** when more detail is needed: Use "1.", "2.", "2.1", "2.2", "3." format to show parent-child relationships.
**Good example** (Japan trip - 7 items, stage-focused):
- 1. Determine travel dates and duration
- 2. Handle visa and documentation
- 3. Book flights and accommodation
- 4. Plan city itineraries
- 5. Arrange local transportation
- 6. Prepare for departure
- 7. Final confirmation before trip
**Bad example** (20+ detailed items):
- Book Tokyo hotel
- Book Kyoto hotel
- Book Osaka hotel
- Buy Suica card
- Download Google Maps
- Download translation app
- ... (too granular!)
</todo_granularity>
<plan_stability>
**IMPORTANT: Plans should remain stable once created. Each conversation has only ONE plan.**
- **Do NOT update plans** when details change (dates, locations, preferences). Instead, update the todos to reflect new information.
- **Only use updatePlan** when the user's goal fundamentally changes (e.g., destination changes from Japan to Korea).
- When user provides more specific information (like exact dates or preferences), **update or add todos** - not the plan.
</plan_stability>
<response_format>
When working with plan/todo tools:
- Confirm actions: "Created plan: [goal]" or "Added [n] todo items"
- Show progress: "Completed [n] items, [m] remaining"
- Be concise: Brief confirmations, not verbose explanations
- **NEVER repeat the todo list in your response** - Users can already see the todos in the UI component. Do not list or enumerate the todo items in your text output.
</response_format>
</plan_and_todos>
`;
export const systemPrompt = `Use Lobe Agent capabilities only when the active model needs built-in assistance. Prefer the active model's native capabilities whenever they are sufficient. Follow each tool's description and schema, and use tool results to answer the user directly.
${planTodoSection}
${subAgentSection}`;
@@ -4,6 +4,11 @@ export const LobeAgentApiName = {
analyzeVisualMedia: 'analyzeVisualMedia',
callSubAgent: 'callSubAgent',
callSubAgents: 'callSubAgents',
clearTodos: 'clearTodos',
createPlan: 'createPlan',
createTodos: 'createTodos',
updatePlan: 'updatePlan',
updateTodos: 'updateTodos',
} as const;
export type LobeAgentApiNameType = (typeof LobeAgentApiName)[keyof typeof LobeAgentApiName];
@@ -95,3 +100,194 @@ export interface CallClientSubAgentsState {
tasks: SubAgentTask[];
type: 'execClientSubAgents';
}
// ==================== Todo Item ====================
/** Status of a todo item */
export type TodoStatus = 'todo' | 'processing' | 'completed';
export interface TodoItem {
/** Status of the todo item */
status: TodoStatus;
/** The todo item text */
text: string;
}
/** Get the next status in the cycle: todo → processing → completed → todo */
export const getNextTodoStatus = (current: TodoStatus): TodoStatus => {
const cycle: TodoStatus[] = ['todo', 'processing', 'completed'];
const index = cycle.indexOf(current);
return cycle[(index + 1) % cycle.length];
};
export interface TodoList {
items: TodoItem[];
updatedAt: string;
}
/** Alias for TodoList, used for state storage in Plan metadata */
export type TodoState = TodoList;
// ==================== Todo Params ====================
/**
* Create new todo items
* - AI input: { adds: string[] } - array of text strings from AI
* - After user edit: { items: TodoItem[] } - saved format with TodoItem objects
*/
export interface CreateTodosParams {
/** Array of text strings from AI */
adds?: string[];
/** Array of TodoItem objects (saved format after user edit) */
items?: TodoItem[];
}
/**
* Update operation types for batch updates
*/
export type TodoUpdateOperationType = 'add' | 'update' | 'remove' | 'complete' | 'processing';
/**
* Single update operation
*/
export interface TodoUpdateOperation {
/** For 'update', 'remove', 'complete', 'processing': the index of the item (0-based) */
index?: number;
/** For 'update': the new text */
newText?: string;
/** For 'update': the new status */
status?: TodoStatus;
/** For 'add': the text to add */
text?: string;
/** Operation type */
type: TodoUpdateOperationType;
}
/**
* Update todo list with batch operations
* Supports: add, update, remove, complete, processing
*/
export interface UpdateTodosParams {
/** Array of update operations to apply */
operations: TodoUpdateOperation[];
}
/**
* Clear todo items
*/
export interface ClearTodosParams {
/** Clear mode: 'completed' only clears done items, 'all' clears everything */
mode: 'completed' | 'all';
}
// ==================== Todo State Types for Render ====================
export interface CreateTodosState {
/** Items that were created */
createdItems: string[];
/** Current todo list after creation */
todos: TodoList;
}
export interface UpdateTodosState {
/** Operations that were applied */
appliedOperations: TodoUpdateOperation[];
/** Current todo list after update */
todos: TodoList;
}
export interface CompleteTodosState {
/** Indices that were completed */
completedIndices: number[];
/** Current todo list after completion */
todos: TodoList;
}
export interface RemoveTodosState {
/** Indices that were removed */
removedIndices: number[];
/** Current todo list after removal */
todos: TodoList;
}
export interface ClearTodosState {
/** Number of items cleared */
clearedCount: number;
/** Mode used for clearing */
mode: 'completed' | 'all';
/** Current todo list after clearing */
todos: TodoList;
}
// ==================== Planning Params ====================
/**
* Create a high-level plan document
* Plans define the strategic direction (what and why), not actionable steps
*
* Field mapping to Document:
* - goal -> document.title
* - description -> document.description
* - context -> document.content
*/
export interface CreatePlanParams {
/** Detailed context, background, constraints (maps to document.content) */
context?: string;
/** Brief summary of the plan (maps to document.description) */
description: string;
/** The main goal or objective to achieve (maps to document.title) */
goal: string;
}
export interface UpdatePlanParams {
/** Mark plan as completed */
completed?: boolean;
/** Updated context (maps to document.content) */
context?: string;
/** Updated description (maps to document.description) */
description?: string;
/** Updated goal (maps to document.title) */
goal?: string;
/** Plan ID to update */
planId: string;
}
// ==================== Plan Result Types ====================
/**
* A high-level plan document
* Contains goal and context, but no steps (steps are managed via Todos)
*
* Field mapping to Document:
* - goal -> document.title
* - description -> document.description
* - context -> document.content
*/
export interface Plan {
/** Whether the plan is completed */
completed: boolean;
/** Detailed context, background, constraints (maps to document.content) */
context?: string;
/** Creation timestamp */
createdAt: string;
/** Brief summary of the plan (maps to document.description) */
description: string;
/** The main goal or objective (maps to document.title) */
goal: string;
/** Unique plan identifier */
id: string;
/** Last update timestamp */
updatedAt: string;
}
// ==================== Plan State Types for Render ====================
export interface CreatePlanState {
/** The created plan document */
plan: Plan;
}
export interface UpdatePlanState {
/** The updated plan document */
plan: Plan;
}
-1
View File
@@ -27,7 +27,6 @@
"@lobechat/builtin-tool-creds": "workspace:*",
"@lobechat/builtin-tool-group-agent-builder": "workspace:*",
"@lobechat/builtin-tool-group-management": "workspace:*",
"@lobechat/builtin-tool-gtd": "workspace:*",
"@lobechat/builtin-tool-knowledge-base": "workspace:*",
"@lobechat/builtin-tool-lobe-agent": "workspace:*",
"@lobechat/builtin-tool-local-system": "workspace:*",
@@ -7,7 +7,6 @@ import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox';
import { CredsManifest } from '@lobechat/builtin-tool-creds';
import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-builder';
import { GroupManagementManifest } from '@lobechat/builtin-tool-group-management';
import { GTDManifest } from '@lobechat/builtin-tool-gtd';
import { KnowledgeBaseManifest } from '@lobechat/builtin-tool-knowledge-base';
import { LobeAgentManifest } from '@lobechat/builtin-tool-lobe-agent';
import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
@@ -31,7 +30,6 @@ export const builtinToolIdentifiers: string[] = [
CredsManifest.identifier,
GroupAgentBuilderManifest.identifier,
GroupManagementManifest.identifier,
GTDManifest.identifier,
KnowledgeBaseManifest.identifier,
LocalSystemManifest.identifier,
MemoryManifest.identifier,
-7
View File
@@ -8,7 +8,6 @@ import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox';
import { CredsManifest } from '@lobechat/builtin-tool-creds';
import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-builder';
import { GroupManagementManifest } from '@lobechat/builtin-tool-group-management';
import { GTDManifest } from '@lobechat/builtin-tool-gtd';
import { KnowledgeBaseManifest } from '@lobechat/builtin-tool-knowledge-base';
import { LobeAgentManifest } from '@lobechat/builtin-tool-lobe-agent';
import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
@@ -43,7 +42,6 @@ export const defaultToolIds = [
CloudSandboxManifest.identifier,
TopicReferenceManifest.identifier,
AgentDocumentsManifest.identifier,
GTDManifest.identifier,
TaskManifest.identifier,
LobeAgentManifest.identifier,
];
@@ -202,11 +200,6 @@ export const builtinTools: LobeBuiltinTool[] = [
manifest: AgentManagementManifest,
type: 'builtin',
},
{
identifier: GTDManifest.identifier,
manifest: GTDManifest,
type: 'builtin',
},
{
identifier: CalculatorManifest.identifier,
manifest: CalculatorManifest,
-2
View File
@@ -30,7 +30,6 @@ import {
GroupManagementInspectors,
GroupManagementManifest,
} from '@lobechat/builtin-tool-group-management/client';
import { GTDInspectors, GTDManifest } from '@lobechat/builtin-tool-gtd/client';
import {
KnowledgeBaseInspectors,
KnowledgeBaseManifest,
@@ -87,7 +86,6 @@ const BuiltinToolInspectors: Record<string, Record<string, BuiltinInspector>> =
string,
BuiltinInspector
>,
[GTDManifest.identifier]: GTDInspectors as Record<string, BuiltinInspector>,
[KnowledgeBaseManifest.identifier]: KnowledgeBaseInspectors as Record<string, BuiltinInspector>,
[LobeAgentManifest.identifier]: LobeAgentInspectors as Record<string, BuiltinInspector>,
[LocalSystemManifest.identifier]: LocalSystemInspectors as Record<string, BuiltinInspector>,
+5 -2
View File
@@ -12,7 +12,10 @@ import {
GroupManagementInterventions,
GroupManagementManifest,
} from '@lobechat/builtin-tool-group-management/client';
import { GTDInterventions, GTDManifest } from '@lobechat/builtin-tool-gtd/client';
import {
LobeAgentInterventions,
LobeAgentManifest,
} from '@lobechat/builtin-tool-lobe-agent/client';
import {
LocalSystemIdentifier,
LocalSystemInterventions,
@@ -39,7 +42,7 @@ export const BuiltinToolInterventions: Record<string, Record<string, any>> = {
[ClaudeCodeIdentifier]: ClaudeCodeInterventions,
[CloudSandboxManifest.identifier]: CloudSandboxInterventions,
[GroupManagementManifest.identifier]: GroupManagementInterventions,
[GTDManifest.identifier]: GTDInterventions,
[LobeAgentManifest.identifier]: LobeAgentInterventions,
[LocalSystemIdentifier]: LocalSystemInterventions,
[MemoryManifest.identifier]: MemoryInterventions,
[MessageManifest.identifier]: MessageInterventions,
-2
View File
@@ -15,7 +15,6 @@ import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-bu
import { GroupAgentBuilderRenders } from '@lobechat/builtin-tool-group-agent-builder/client';
import { GroupManagementManifest } from '@lobechat/builtin-tool-group-management';
import { GroupManagementRenders } from '@lobechat/builtin-tool-group-management/client';
import { GTDManifest, GTDRenders } from '@lobechat/builtin-tool-gtd/client';
import {
KnowledgeBaseManifest,
KnowledgeBaseRenders,
@@ -64,7 +63,6 @@ const BuiltinToolsRenders: Record<string, Record<string, BuiltinRender>> = {
[CloudSandboxManifest.identifier]: CloudSandboxRenders as Record<string, BuiltinRender>,
[GroupAgentBuilderManifest.identifier]: GroupAgentBuilderRenders as Record<string, BuiltinRender>,
[GroupManagementManifest.identifier]: GroupManagementRenders as Record<string, BuiltinRender>,
[GTDManifest.identifier]: GTDRenders as Record<string, BuiltinRender>,
[KnowledgeBaseManifest.identifier]: KnowledgeBaseRenders as Record<string, BuiltinRender>,
[LobeAgentManifest.identifier]: LobeAgentRenders as Record<string, BuiltinRender>,
[LocalSystemManifest.identifier]: LocalSystemRenders as Record<string, BuiltinRender>,
-2
View File
@@ -26,7 +26,6 @@ import {
GroupManagementManifest,
GroupManagementStreamings,
} from '@lobechat/builtin-tool-group-management/client';
import { GTDManifest, GTDStreamings } from '@lobechat/builtin-tool-gtd/client';
import { LobeAgentManifest, LobeAgentStreamings } from '@lobechat/builtin-tool-lobe-agent/client';
import {
LocalSystemManifest,
@@ -61,7 +60,6 @@ const BuiltinToolStreamings: Record<string, Record<string, BuiltinStreaming>> =
string,
BuiltinStreaming
>,
[GTDManifest.identifier]: GTDStreamings as Record<string, BuiltinStreaming>,
[LobeAgentManifest.identifier]: LobeAgentStreamings as Record<string, BuiltinStreaming>,
[LocalSystemManifest.identifier]: LocalSystemStreamings as Record<string, BuiltinStreaming>,
[MemoryManifest.identifier]: MemoryStreamings as Record<string, BuiltinStreaming>,
-1
View File
@@ -14,7 +14,6 @@ export const RECOMMENDED_SKILLS: RecommendedSkillItem[] = [
{ id: 'lobe-artifacts', type: RecommendedSkillType.Builtin },
{ id: 'lobe-user-memory', type: RecommendedSkillType.Builtin },
{ id: 'lobe-cloud-sandbox', type: RecommendedSkillType.Builtin },
{ id: 'lobe-gtd', type: RecommendedSkillType.Builtin },
{ id: 'lobe-task', type: RecommendedSkillType.Builtin },
{ id: 'lobe-agent-documents', type: RecommendedSkillType.Builtin },
{ id: 'lobe-message', type: RecommendedSkillType.Builtin },
@@ -1391,7 +1391,7 @@ describe('GoogleGenerativeAIStream', () => {
parts: [
{
functionCall: {
name: 'lobe-gtd____createPlan',
name: 'lobe-agent____createPlan',
args: {
goal: 'Fix Linear API Argument Validation Error',
description: 'Investigate the Linear API error.',
@@ -1424,7 +1424,7 @@ describe('GoogleGenerativeAIStream', () => {
parts: [
{
functionCall: {
name: 'lobe-gtd____createTodos',
name: 'lobe-agent____createTodos',
args: {
adds: [
'Verify Linear GraphQL API requirements',
@@ -1498,12 +1498,12 @@ describe('GoogleGenerativeAIStream', () => {
// First tool call (createPlan)
'id: chat_test',
'event: tool_calls',
'data: [{"function":{"arguments":"{\\"goal\\":\\"Fix Linear API Argument Validation Error\\",\\"description\\":\\"Investigate the Linear API error.\\",\\"context\\":\\"The user is encountering a validation error.\\"}","name":"lobe-gtd____createPlan"},"id":"lobe-gtd____createPlan_0_tool_id_1","index":0,"thoughtSignature":"EoIYCv8XAXLI2nx+C18votz5l0A...","type":"function"}]\n',
'data: [{"function":{"arguments":"{\\"goal\\":\\"Fix Linear API Argument Validation Error\\",\\"description\\":\\"Investigate the Linear API error.\\",\\"context\\":\\"The user is encountering a validation error.\\"}","name":"lobe-agent____createPlan"},"id":"lobe-agent____createPlan_0_tool_id_1","index":0,"thoughtSignature":"EoIYCv8XAXLI2nx+C18votz5l0A...","type":"function"}]\n',
// Second tool call (createTodos) - should be a SEPARATE event with index:0
'id: chat_test',
'event: tool_calls',
'data: [{"function":{"arguments":"{\\"adds\\":[\\"Verify Linear GraphQL API requirements\\",\\"Determine if code needs to look up Team UUID\\",\\"Provide corrected code\\"]}","name":"lobe-gtd____createTodos"},"id":"lobe-gtd____createTodos_0_tool_id_2","index":0,"type":"function"}]\n',
'data: [{"function":{"arguments":"{\\"adds\\":[\\"Verify Linear GraphQL API requirements\\",\\"Determine if code needs to look up Team UUID\\",\\"Provide corrected code\\"]}","name":"lobe-agent____createTodos"},"id":"lobe-agent____createTodos_0_tool_id_2","index":0,"type":"function"}]\n',
// Stop and usage
'id: chat_test',
+1 -1
View File
@@ -15,7 +15,7 @@ export type StepContextTodoStatus = 'todo' | 'processing' | 'completed';
/**
* Todo item structure
* Duplicated here to avoid circular dependency with builtin-tool-gtd
* Duplicated here to avoid circular dependency with builtin-tool-lobe-agent
*/
export interface StepContextTodoItem {
status: StepContextTodoStatus;
@@ -16,7 +16,6 @@ import lobeAgentManagement from './lobe-agent-management';
import lobeCloudSandbox from './lobe-cloud-sandbox';
import lobeGroupAgentBuilder from './lobe-group-agent-builder';
import lobeGroupManagement from './lobe-group-management';
import lobeGtd from './lobe-gtd';
import lobeKnowledgeBase from './lobe-knowledge-base';
import lobeLocalSystem from './lobe-local-system';
import lobeNotebook from './lobe-notebook';
@@ -75,7 +74,6 @@ const toolsetModules: ToolsetFixtureModule[] = [
lobeCloudSandbox,
lobeGroupAgentBuilder,
lobeGroupManagement,
lobeGtd,
lobeKnowledgeBase,
lobeLocalSystem,
lobeNotebook,
@@ -1,6 +1,6 @@
'use client';
import { defineFixtures, single } from './_helpers';
import { defineFixtures, single, variants } from './_helpers';
export default defineFixtures({
identifier: 'lobe-agent',
@@ -28,5 +28,85 @@ export default defineFixtures({
],
},
}),
clearTodos: single({
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Capture real stream data' },
{ status: 'processing', text: 'Build /devtools route' },
],
},
},
}),
createPlan: single({
pluginState: {
plan: {
context:
'We want a reusable development-only page that renders every registered builtin tool card with a stable sample fixture.',
description: 'Create a maintainable preview harness for builtin tool renders.',
goal: 'Build /devtools render preview',
id: 'plan_devtools_preview',
},
},
}),
createTodos: variants([
{
label: 'Mixed',
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Enumerate all render entries' },
{ status: 'processing', text: 'Create preview fixtures' },
{ status: 'todo', text: 'Smoke test the route locally' },
],
},
},
},
{
label: 'Many todos',
pluginState: {
todos: {
items: Array.from({ length: 10 }, (_, i) => ({
status: i < 3 ? 'completed' : i < 5 ? 'processing' : 'todo',
text: `Subtask ${i + 1}: prepare fixtures for batch ${i + 1}`,
})),
},
},
},
{
label: 'All done',
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Enumerate render entries' },
{ status: 'completed', text: 'Author preview fixtures' },
{ status: 'completed', text: 'Smoke-test the gallery' },
],
},
},
},
]),
updatePlan: single({
pluginState: {
plan: {
context:
'The route is now in place; expand the preview harness by keeping fixture data next to the page.',
description: 'Track the follow-up work for richer render fixtures.',
goal: 'Expand /devtools coverage',
id: 'plan_devtools_preview',
},
},
}),
updateTodos: single({
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Export render registry entries' },
{ status: 'processing', text: 'Hydrate grouped task fixtures' },
{ status: 'todo', text: 'Add richer missing-state cases' },
],
},
},
}),
},
});
@@ -1,89 +0,0 @@
'use client';
import { defineFixtures, single, variants } from './_helpers';
export default defineFixtures({
identifier: 'lobe-gtd',
fixtures: {
clearTodos: single({
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Capture real stream data' },
{ status: 'processing', text: 'Build /devtools route' },
],
},
},
}),
createPlan: single({
pluginState: {
plan: {
context:
'We want a reusable development-only page that renders every registered builtin tool card with a stable sample fixture.',
description: 'Create a maintainable preview harness for builtin tool renders.',
goal: 'Build /devtools render preview',
id: 'plan_devtools_preview',
},
},
}),
createTodos: variants([
{
label: 'Mixed',
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Enumerate all render entries' },
{ status: 'processing', text: 'Create preview fixtures' },
{ status: 'todo', text: 'Smoke test the route locally' },
],
},
},
},
{
label: 'Many todos',
pluginState: {
todos: {
items: Array.from({ length: 10 }, (_, i) => ({
status: i < 3 ? 'completed' : i < 5 ? 'processing' : 'todo',
text: `Subtask ${i + 1}: prepare fixtures for batch ${i + 1}`,
})),
},
},
},
{
label: 'All done',
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Enumerate render entries' },
{ status: 'completed', text: 'Author preview fixtures' },
{ status: 'completed', text: 'Smoke-test the gallery' },
],
},
},
},
]),
updatePlan: single({
pluginState: {
plan: {
context:
'The route is now in place; expand the preview harness by keeping fixture data next to the page.',
description: 'Track the follow-up work for richer render fixtures.',
goal: 'Expand /devtools coverage',
id: 'plan_devtools_preview',
},
},
}),
updateTodos: single({
pluginState: {
todos: {
items: [
{ status: 'completed', text: 'Export render registry entries' },
{ status: 'processing', text: 'Hydrate grouped task fixtures' },
{ status: 'todo', text: 'Add richer missing-state cases' },
],
},
},
}),
},
});
+11 -14
View File
@@ -124,20 +124,17 @@ export default {
'builtins.lobe-group-management.inspector.executeAgentTasks.title': 'Assigning tasks to:',
'builtins.lobe-group-management.inspector.speak.title': 'Designated Agent speaks:',
'builtins.lobe-group-management.title': 'Group Coordinator',
'builtins.lobe-gtd.apiName.clearTodos': 'Clear todos',
'builtins.lobe-gtd.apiName.clearTodos.modeAll': 'all',
'builtins.lobe-gtd.apiName.clearTodos.modeCompleted': 'completed',
'builtins.lobe-gtd.apiName.clearTodos.result': 'Clear <mode>{{mode}}</mode> todos',
'builtins.lobe-gtd.apiName.completeTodos': 'Complete todos',
'builtins.lobe-gtd.apiName.createPlan': 'Create plan',
'builtins.lobe-gtd.apiName.createPlan.result': 'Create plan: <goal>{{goal}}</goal>',
'builtins.lobe-gtd.apiName.createTodos': 'Create todos',
'builtins.lobe-gtd.apiName.removeTodos': 'Delete todos',
'builtins.lobe-gtd.apiName.updatePlan': 'Update plan',
'builtins.lobe-gtd.apiName.updatePlan.completed': 'Completed',
'builtins.lobe-gtd.apiName.updatePlan.modified': 'Modified',
'builtins.lobe-gtd.apiName.updateTodos': 'Update todos',
'builtins.lobe-gtd.title': 'Task Tools',
'builtins.lobe-agent.apiName.clearTodos': 'Clear todos',
'builtins.lobe-agent.apiName.clearTodos.modeAll': 'all',
'builtins.lobe-agent.apiName.clearTodos.modeCompleted': 'completed',
'builtins.lobe-agent.apiName.clearTodos.result': 'Clear <mode>{{mode}}</mode> todos',
'builtins.lobe-agent.apiName.createPlan': 'Create plan',
'builtins.lobe-agent.apiName.createPlan.result': 'Create plan: <goal>{{goal}}</goal>',
'builtins.lobe-agent.apiName.createTodos': 'Create todos',
'builtins.lobe-agent.apiName.updatePlan': 'Update plan',
'builtins.lobe-agent.apiName.updatePlan.completed': 'Completed',
'builtins.lobe-agent.apiName.updatePlan.modified': 'Modified',
'builtins.lobe-agent.apiName.updateTodos': 'Update todos',
'builtins.lobe-knowledge-base.apiName.readKnowledge': 'Read Library content',
'builtins.lobe-knowledge-base.apiName.searchKnowledgeBase': 'Search Library',
'builtins.lobe-knowledge-base.inspector.andMoreFiles': 'and {{count}} more',
-4
View File
@@ -1049,10 +1049,6 @@ When I am ___, I need ___
'tools.builtins.lobe-cloud-sandbox.readme':
'Execute Python, JavaScript, and TypeScript code in an isolated cloud environment. Run shell commands, manage files, search content with regex, and export results securely.',
'tools.builtins.lobe-cloud-sandbox.title': 'Cloud Sandbox',
'tools.builtins.lobe-gtd.description': 'Plan goals and track progress with GTD methodology',
'tools.builtins.lobe-gtd.readme':
'Plan goals and track progress using GTD methodology. Create strategic plans, manage todo lists with status tracking, and execute long-running async tasks.',
'tools.builtins.lobe-gtd.title': 'GTD Tools',
'tools.builtins.lobe-local-system.description':
'Access and manage local files, run shell commands on your desktop',
'tools.builtins.lobe-local-system.readme':
+45 -45
View File
@@ -59,51 +59,51 @@ export default {
'dalle.generating': 'Generating...',
'dalle.images': 'Images:',
'dalle.prompt': 'Prompt',
'lobe-gtd.actions.add': 'Add',
'lobe-gtd.actions.clearCompleted': 'Clear Completed',
'lobe-gtd.actions.placeholder': 'Enter a to-do item...',
'lobe-gtd.addTodo.placeholder': 'Add a todo item...',
'lobe-gtd.clearTodos.cleared': '{{count}} item(s) cleared',
'lobe-gtd.clearTodos.clearedCompleted': '{{count}} completed item(s) cleared',
'lobe-gtd.clearTodos.clearedCompleted_one': '{{count}} completed item cleared',
'lobe-gtd.clearTodos.clearedCompleted_other': '{{count}} completed items cleared',
'lobe-gtd.clearTodos.cleared_one': '{{count}} item cleared',
'lobe-gtd.clearTodos.cleared_other': '{{count}} items cleared',
'lobe-gtd.clearTodos.header': 'Clear Todo Items',
'lobe-gtd.clearTodos.label': 'Choose what to clear:',
'lobe-gtd.clearTodos.noItems': 'No items to clear',
'lobe-gtd.clearTodos.option.all': 'Clear all items (including pending)',
'lobe-gtd.clearTodos.option.completed': 'Clear completed items only',
'lobe-gtd.clearTodos.remaining': '{{count}} item(s) remaining',
'lobe-gtd.clearTodos.remaining_one': '{{count}} item remaining',
'lobe-gtd.clearTodos.remaining_other': '{{count}} items remaining',
'lobe-gtd.completeTodos.completed': '{{count}} item(s) completed',
'lobe-gtd.completeTodos.completed_one': '{{count}} item completed',
'lobe-gtd.completeTodos.completed_other': '{{count}} items completed',
'lobe-gtd.createPlan.context.label': 'Context (optional)',
'lobe-gtd.createPlan.context.placeholder': 'Background, constraints, considerations...',
'lobe-gtd.createPlan.description.label': 'Description',
'lobe-gtd.createPlan.description.placeholder': 'Brief summary of the plan',
'lobe-gtd.createPlan.goal.label': 'Goal',
'lobe-gtd.createPlan.goal.placeholder': 'What do you want to achieve?',
'lobe-gtd.createTodos.created': '{{count}} to-do item(s) created',
'lobe-gtd.createTodos.created_one': '{{count}} to-do item created',
'lobe-gtd.createTodos.created_other': '{{count}} to-do items created',
'lobe-gtd.createTodos.total': 'Total: {{count}} item(s)',
'lobe-gtd.createTodos.total_one': 'Total: {{count}} item',
'lobe-gtd.createTodos.total_other': 'Total: {{count}} items',
'lobe-gtd.removeTodos.removed': '{{count}} item(s) removed',
'lobe-gtd.removeTodos.removed_one': '{{count}} item removed',
'lobe-gtd.removeTodos.removed_other': '{{count}} items removed',
'lobe-gtd.status.done': '{{count}} completed',
'lobe-gtd.status.pending': '{{count}} pending',
'lobe-gtd.todoItem.placeholder': 'Enter todo item...',
'lobe-gtd.todoList.empty': 'To-do list is empty',
'lobe-gtd.todoList.items': '{{count}} item(s)',
'lobe-gtd.todoList.items_one': '{{count}} item',
'lobe-gtd.todoList.items_other': '{{count}} items',
'lobe-gtd.todoList.title': 'To-Do List',
'lobe-gtd.updateTodos.updated': 'To-do list updated',
'lobe-agent.actions.add': 'Add',
'lobe-agent.actions.clearCompleted': 'Clear Completed',
'lobe-agent.actions.placeholder': 'Enter a to-do item...',
'lobe-agent.addTodo.placeholder': 'Add a todo item...',
'lobe-agent.clearTodos.cleared': '{{count}} item(s) cleared',
'lobe-agent.clearTodos.clearedCompleted': '{{count}} completed item(s) cleared',
'lobe-agent.clearTodos.clearedCompleted_one': '{{count}} completed item cleared',
'lobe-agent.clearTodos.clearedCompleted_other': '{{count}} completed items cleared',
'lobe-agent.clearTodos.cleared_one': '{{count}} item cleared',
'lobe-agent.clearTodos.cleared_other': '{{count}} items cleared',
'lobe-agent.clearTodos.header': 'Clear Todo Items',
'lobe-agent.clearTodos.label': 'Choose what to clear:',
'lobe-agent.clearTodos.noItems': 'No items to clear',
'lobe-agent.clearTodos.option.all': 'Clear all items (including pending)',
'lobe-agent.clearTodos.option.completed': 'Clear completed items only',
'lobe-agent.clearTodos.remaining': '{{count}} item(s) remaining',
'lobe-agent.clearTodos.remaining_one': '{{count}} item remaining',
'lobe-agent.clearTodos.remaining_other': '{{count}} items remaining',
'lobe-agent.completeTodos.completed': '{{count}} item(s) completed',
'lobe-agent.completeTodos.completed_one': '{{count}} item completed',
'lobe-agent.completeTodos.completed_other': '{{count}} items completed',
'lobe-agent.createPlan.context.label': 'Context (optional)',
'lobe-agent.createPlan.context.placeholder': 'Background, constraints, considerations...',
'lobe-agent.createPlan.description.label': 'Description',
'lobe-agent.createPlan.description.placeholder': 'Brief summary of the plan',
'lobe-agent.createPlan.goal.label': 'Goal',
'lobe-agent.createPlan.goal.placeholder': 'What do you want to achieve?',
'lobe-agent.createTodos.created': '{{count}} to-do item(s) created',
'lobe-agent.createTodos.created_one': '{{count}} to-do item created',
'lobe-agent.createTodos.created_other': '{{count}} to-do items created',
'lobe-agent.createTodos.total': 'Total: {{count}} item(s)',
'lobe-agent.createTodos.total_one': 'Total: {{count}} item',
'lobe-agent.createTodos.total_other': 'Total: {{count}} items',
'lobe-agent.removeTodos.removed': '{{count}} item(s) removed',
'lobe-agent.removeTodos.removed_one': '{{count}} item removed',
'lobe-agent.removeTodos.removed_other': '{{count}} items removed',
'lobe-agent.status.done': '{{count}} completed',
'lobe-agent.status.pending': '{{count}} pending',
'lobe-agent.todoItem.placeholder': 'Enter todo item...',
'lobe-agent.todoList.empty': 'To-do list is empty',
'lobe-agent.todoList.items': '{{count}} item(s)',
'lobe-agent.todoList.items_one': '{{count}} item',
'lobe-agent.todoList.items_other': '{{count}} items',
'lobe-agent.todoList.title': 'To-Do List',
'lobe-agent.updateTodos.updated': 'To-do list updated',
'lobe-knowledge-base.readKnowledge.meta.chars': 'Character Count',
'lobe-knowledge-base.readKnowledge.meta.lines': 'Line Count',
'localFiles.editFile.newString': 'Replace with',
@@ -14,7 +14,6 @@ import { briefRuntime } from './brief';
import { calculatorRuntime } from './calculator';
import { cloudSandboxRuntime } from './cloudSandbox';
import { credsRuntime } from './creds';
import { gtdRuntime } from './gtd';
import { knowledgeBaseRuntime } from './knowledgeBase';
import { lobeAgentRuntime } from './lobeAgent';
import { localSystemRuntime } from './localSystem';
@@ -68,7 +67,6 @@ registerRuntimes([
topicReferenceRuntime,
userInteractionRuntime,
credsRuntime,
gtdRuntime,
knowledgeBaseRuntime,
webOnboardingRuntime,
lobeAgentRuntime,
@@ -7,6 +7,7 @@ import {
hasUserVisualFiles,
LobeAgentIdentifier,
normalizeAnalyzeVisualMediaInput,
PlanExecutionRuntime,
selectVisualFileItems,
validateVisualMediaUrls,
} from '@lobechat/builtin-tool-lobe-agent';
@@ -22,6 +23,7 @@ import { toolsEnv } from '@/envs/tools';
import { initModelRuntimeFromDB } from '@/server/modules/ModelRuntime';
import { FileService } from '@/server/services/file';
import { createServerPlanRuntimeService } from './lobeAgentPlan';
import type { ServerRuntimeRegistration } from './types';
interface AnalyzeVisualMediaParams {
@@ -69,6 +71,7 @@ class LobeAgentExecutionRuntime {
private messageId: string;
private threadId?: string | null;
private topicId?: string;
private planRuntime: PlanExecutionRuntime;
constructor(context: LobeAgentRuntimeContext) {
this.agentId = context.agentId;
@@ -78,8 +81,28 @@ class LobeAgentExecutionRuntime {
this.threadId = context.threadId;
this.topicId = context.topicId;
this.userId = context.userId;
this.planRuntime = new PlanExecutionRuntime(
createServerPlanRuntimeService(context.serverDB, context.userId),
);
}
// ==================== Plan / Todo (delegated to PlanExecutionRuntime) ====================
createPlan = (params: any) =>
this.planRuntime.createPlan(params, { messageId: this.messageId, topicId: this.topicId });
updatePlan = (params: any) =>
this.planRuntime.updatePlan(params, { messageId: this.messageId, topicId: this.topicId });
createTodos = (params: any) =>
this.planRuntime.createTodos(params, { messageId: this.messageId, topicId: this.topicId });
updateTodos = (params: any) =>
this.planRuntime.updateTodos(params, { messageId: this.messageId, topicId: this.topicId });
clearTodos = (params: any) =>
this.planRuntime.clearTodos(params, { messageId: this.messageId, topicId: this.topicId });
private queryScopeMessages = (
messageModel: MessageModel,
sourceMessage: ServerVisualSourceMessage,
@@ -1,19 +1,22 @@
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
import {
GTDExecutionRuntime,
type GTDRuntimeService,
type PlanDocument,
} from '@lobechat/builtin-tool-gtd/executionRuntime';
import { type PlanDocument, type PlanRuntimeService } from '@lobechat/builtin-tool-lobe-agent';
import { type LobeChatDatabase } from '@lobechat/database';
import { DocumentModel } from '@/database/models/document';
import { TopicDocumentModel } from '@/database/models/topicDocument';
import { type ServerRuntimeRegistration } from './types';
const PLAN_FILE_TYPE = 'agent/plan';
const createGTDRuntimeService = (serverDB: LobeChatDatabase, userId: string): GTDRuntimeService => {
/**
* Build a server-side `PlanRuntimeService` backed by the application database.
*
* The factory is consumed by `lobeAgent.ts`'s `LobeAgentExecutionRuntime`,
* which folds plan/todo execution into the lobe-agent server runtime so the
* registry has a single runtime per identifier.
*/
export const createServerPlanRuntimeService = (
serverDB: LobeChatDatabase,
userId: string,
): PlanRuntimeService => {
const documentModel = new DocumentModel(serverDB, userId);
const topicDocumentModel = new TopicDocumentModel(serverDB, userId);
@@ -47,7 +50,7 @@ const createGTDRuntimeService = (serverDB: LobeChatDatabase, userId: string): GT
content,
description,
fileType: PLAN_FILE_TYPE,
source: `gtd:${topicId}`,
source: `lobe-agent:${topicId}`,
sourceType: 'api',
title: goal,
totalCharCount: content.length,
@@ -93,15 +96,3 @@ const createGTDRuntimeService = (serverDB: LobeChatDatabase, userId: string): GT
},
};
};
export const gtdRuntime: ServerRuntimeRegistration = {
factory: (context) => {
if (!context.userId || !context.serverDB) {
throw new Error('userId and serverDB are required for GTD tool execution');
}
const service = createGTDRuntimeService(context.serverDB, context.userId);
return new GTDExecutionRuntime(service);
},
identifier: GTDIdentifier,
};
@@ -1,6 +1,6 @@
import * as builtinAgents from '@lobechat/builtin-agents';
import { GroupManagementIdentifier } from '@lobechat/builtin-tool-group-management';
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
import { LobeAgentIdentifier } from '@lobechat/builtin-tool-lobe-agent';
import { NotebookIdentifier } from '@lobechat/builtin-tool-notebook';
import { PageAgentIdentifier } from '@lobechat/builtin-tool-page-agent';
import { TaskIdentifier } from '@lobechat/builtin-tool-task';
@@ -464,13 +464,13 @@ describe('resolveAgentConfig', () => {
it('should include GTD and Notebook tools in plugins', () => {
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
plugins: [GTDIdentifier, NotebookIdentifier],
plugins: [LobeAgentIdentifier, NotebookIdentifier],
systemRole: 'Inbox system role',
});
const result = resolveAgentConfig({ agentId: 'inbox-agent' });
expect(result.plugins).toContain(GTDIdentifier);
expect(result.plugins).toContain(LobeAgentIdentifier);
expect(result.plugins).toContain(NotebookIdentifier);
expect(result.isBuiltinAgent).toBe(true);
expect(result.slug).toBe('inbox');
@@ -478,7 +478,7 @@ describe('resolveAgentConfig', () => {
it('should preserve user plugins while including GTD and Notebook', () => {
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
plugins: [GTDIdentifier, NotebookIdentifier, 'user-plugin'],
plugins: [LobeAgentIdentifier, NotebookIdentifier, 'user-plugin'],
systemRole: 'Inbox system role',
});
@@ -487,7 +487,7 @@ describe('resolveAgentConfig', () => {
plugins: ['user-plugin'],
});
expect(result.plugins).toContain(GTDIdentifier);
expect(result.plugins).toContain(LobeAgentIdentifier);
expect(result.plugins).toContain(NotebookIdentifier);
expect(result.plugins).toContain('user-plugin');
});
@@ -510,8 +510,8 @@ describe('resolveAgentConfig', () => {
const getAgentRuntimeConfigSpy = vi
.spyOn(builtinAgents, 'getAgentRuntimeConfig')
.mockImplementation((slug, ctx) => ({
// This simulates the actual INBOX runtime: [GTDIdentifier, NotebookIdentifier, ...(ctx.plugins || [])]
plugins: [GTDIdentifier, NotebookIdentifier, ...(ctx.plugins || [])],
// This simulates the actual INBOX runtime: [LobeAgentIdentifier, NotebookIdentifier, ...(ctx.plugins || [])]
plugins: [LobeAgentIdentifier, NotebookIdentifier, ...(ctx.plugins || [])],
systemRole: 'Inbox system role',
}));
@@ -527,7 +527,7 @@ describe('resolveAgentConfig', () => {
);
// Verify final plugins include both builtin tools AND user-configured plugins
expect(result.plugins).toContain(GTDIdentifier);
expect(result.plugins).toContain(LobeAgentIdentifier);
expect(result.plugins).toContain(NotebookIdentifier);
expect(result.plugins).toContain('web-search');
expect(result.plugins).toContain('memory');
@@ -853,7 +853,7 @@ describe('resolveAgentConfig', () => {
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
chatConfig: { enableHistoryCount: false },
plugins: [GroupManagementIdentifier, GTDIdentifier],
plugins: [GroupManagementIdentifier, LobeAgentIdentifier],
systemRole: 'You are a group supervisor...',
});
@@ -888,7 +888,7 @@ describe('resolveAgentConfig', () => {
// Mock: getAgentRuntimeConfig for supervisor agent
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
chatConfig: { enableHistoryCount: false },
plugins: [GroupManagementIdentifier, GTDIdentifier],
plugins: [GroupManagementIdentifier, LobeAgentIdentifier],
systemRole: 'You are a group supervisor...',
});
@@ -901,7 +901,7 @@ describe('resolveAgentConfig', () => {
expect(result.isBuiltinAgent).toBe(true);
expect(result.slug).toBe('group-supervisor');
expect(result.plugins).toContain(GroupManagementIdentifier);
expect(result.plugins).toContain(GTDIdentifier);
expect(result.plugins).toContain(LobeAgentIdentifier);
});
it('should pass groupSupervisorContext to getAgentRuntimeConfig', () => {
@@ -1022,7 +1022,7 @@ describe('resolveAgentConfig', () => {
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
chatConfig: { enableHistoryCount: false },
plugins: [GroupManagementIdentifier, GTDIdentifier],
plugins: [GroupManagementIdentifier, LobeAgentIdentifier],
systemRole: 'Supervisor system role',
});
@@ -9,7 +9,7 @@ import {
type KlavisServiceSummary,
} from '@lobechat/builtin-tool-creds';
import { GroupAgentBuilderIdentifier } from '@lobechat/builtin-tool-group-agent-builder';
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
import { LobeAgentIdentifier } from '@lobechat/builtin-tool-lobe-agent';
import { PageAgentIdentifier } from '@lobechat/builtin-tool-page-agent';
import { WebOnboardingIdentifier } from '@lobechat/builtin-tool-web-onboarding';
import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
@@ -317,9 +317,9 @@ export const contextEngineering = async ({
userMemoryData = combineUserMemoryData(topicMemories, persona);
}
// Resolve GTD context: plan and todos
// GTD tool must be enabled and topicId must be provided
const isGTDEnabled = tools?.includes(GTDIdentifier) ?? false;
// Resolve plan + todos context (now part of the lobe-agent tool).
// Lobe-agent must be enabled and topicId must be provided.
const isGTDEnabled = tools?.includes(LobeAgentIdentifier) ?? false;
let gtdConfig: GTDConfig | undefined;
if (isGTDEnabled && topicId) {
@@ -13,7 +13,7 @@ describe('selectTodosFromMessages', () => {
role: 'tool',
content: 'Todos updated',
plugin: {
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'createTodos',
arguments: '{}',
},
@@ -127,7 +127,7 @@ describe('selectTodosFromMessages', () => {
role: 'tool',
content: 'Something',
plugin: {
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'someOtherApi',
arguments: '{}',
},
@@ -149,7 +149,7 @@ describe('selectTodosFromMessages', () => {
role: 'tool',
content: 'Todos',
plugin: {
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'createTodos',
arguments: '{}',
},
@@ -242,7 +242,7 @@ describe('selectTodosFromMessages', () => {
role: 'tool',
content: 'Todos',
plugin: {
identifier: 'lobe-gtd',
identifier: 'lobe-agent',
apiName: 'createTodos',
arguments: '{}',
},
@@ -271,7 +271,7 @@ describe('selectCurrentTurnTodosFromMessages', () => {
id: `tool-${text}`,
role: 'tool',
content: 'Todos updated',
plugin: { identifier: 'lobe-gtd', apiName: 'createTodos', arguments: '{}' },
plugin: { identifier: 'lobe-agent', apiName: 'createTodos', arguments: '{}' },
pluginState: {
todos: { items: [{ text, status }], updatedAt: '2026-04-20T00:00:00.000Z' },
},
@@ -11,9 +11,8 @@ import { cloudSandboxExecutor } from '@lobechat/builtin-tool-cloud-sandbox/execu
import { credsExecutor } from '@lobechat/builtin-tool-creds/executor';
import { groupAgentBuilderExecutor } from '@lobechat/builtin-tool-group-agent-builder/executor';
import { groupManagementExecutor } from '@lobechat/builtin-tool-group-management/executor';
import { gtdExecutor } from '@lobechat/builtin-tool-gtd/executor';
import { knowledgeBaseExecutor } from '@lobechat/builtin-tool-knowledge-base/client';
import { lobeAgentExecutor } from '@lobechat/builtin-tool-lobe-agent/executor';
import { lobeAgentExecutor } from '@lobechat/builtin-tool-lobe-agent/client';
import { localSystemExecutor } from '@lobechat/builtin-tool-local-system/executor';
import { memoryExecutor } from '@lobechat/builtin-tool-memory/executor';
import { taskExecutor } from '@lobechat/builtin-tool-task/executor';
@@ -141,7 +140,6 @@ registerExecutors([
credsExecutor,
groupAgentBuilderExecutor,
groupManagementExecutor,
gtdExecutor,
knowledgeBaseExecutor,
localSystemExecutor,
memoryExecutor,