Skip to content

feat: task lists #6906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions core/context/taskList/TaskManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { v4 as uuidv4 } from "uuid";
import { TaskInfo } from "../..";
import type { FromCoreProtocol, ToCoreProtocol } from "../../protocol";
import type { IMessenger } from "../../protocol/messenger";

export enum TaskStatus {
Pending = "pending",
Completed = "completed",
}

export interface TaskEvent {
type: "add" | "update" | "remove";
tasks: TaskInfo[];
}

export class TaskManager {
private taskMap = new Map<TaskInfo["task_id"], TaskInfo>();

constructor(
private messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {}

private emitEvent(eventType: TaskEvent["type"]): void {
this.messenger.send("taskEvent", {
type: eventType,
tasks: this.list(),
});
}

add(name: string, description: string) {
const taskId = uuidv4();
const task: TaskInfo = {
task_id: taskId,
name,
description,
status: TaskStatus.Pending,
metadata: {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
};
this.taskMap.set(taskId, task);

this.emitEvent("add");

return taskId;
}

update(taskId: TaskInfo["task_id"], name: string, description: string) {
const previousTask = this.taskMap.get(taskId);
if (!previousTask) {
throw new Error(`Task with id "${taskId}" not found`);
}

const updatedTask: TaskInfo = {
...previousTask,
name,
description,
metadata: {
...previousTask.metadata,
updatedAt: new Date().toISOString(),
},
};

this.taskMap.set(taskId, updatedTask);

this.emitEvent("update");
}

remove(taskId: TaskInfo["task_id"]) {
const task = this.taskMap.get(taskId);
if (!task) {
return;
}

this.taskMap.delete(taskId);

this.emitEvent("remove");
}

list() {
return Array.from(this.taskMap.values());
}

setTaskStatus(taskId: TaskInfo["task_id"], status: TaskStatus) {
if (!this.taskMap.has(taskId)) {
throw new Error(`Task with id "${taskId}" not found`);
}
this.taskMap.set(taskId, {
...this.taskMap.get(taskId)!,
status,
});
}

getTaskById(taskId: TaskInfo["task_id"]) {
if (!this.taskMap.has(taskId)) {
throw new Error(`Task with id "${taskId}" not found`);
}
return this.taskMap.get(taskId)!;
}
}
25 changes: 25 additions & 0 deletions core/context/taskList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Session } from "../..";
import type { FromCoreProtocol, ToCoreProtocol } from "../../protocol";
import type { IMessenger } from "../../protocol/messenger";
import { TaskManager } from "./TaskManager";

// in memory storage for storing individual task lists
const taskManagers = new Map<Session["sessionId"], TaskManager>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to make this a class for testing but not that important
But should add tests


export async function getSessionTaskManager(
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
const sessionId = await messenger.request("getCurrentSessionId", undefined);
if (taskManagers.has(sessionId)) {
return taskManagers.get(sessionId)!;
}
const newManager = new TaskManager(messenger);
taskManagers.set(sessionId, newManager);
return newManager;
}

export async function fetchTaskList(
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
return (await getSessionTaskManager(messenger)).list();
}
6 changes: 6 additions & 0 deletions core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
} from "./config/onboarding";
import { createNewWorkspaceBlockFile } from "./config/workspace/workspaceBlocks";
import { MCPManagerSingleton } from "./context/mcp/MCPManagerSingleton";
import { fetchTaskList } from "./context/taskList";
import { setMdmLicenseKey } from "./control-plane/mdm/mdm";
import { ApplyAbortManager } from "./edit/applyAbortManager";
import { streamDiffLines } from "./edit/streamDiffLines";
Expand Down Expand Up @@ -920,6 +921,10 @@ export class Core {
const isValid = setMdmLicenseKey(licenseKey);
return isValid;
});

on("taskList/list", ({ data }) => {
return fetchTaskList(this.messenger);
});
}

private async handleToolCall(toolCall: ToolCall) {
Expand Down Expand Up @@ -958,6 +963,7 @@ export class Core {
toolCallId: toolCall.id,
onPartialOutput,
codeBaseIndexer: this.codeBaseIndexer,
messenger: this.messenger,
});

return result;
Expand Down
18 changes: 18 additions & 0 deletions core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import {
PromptTemplates,
} from "@continuedev/config-yaml";
import Parser from "web-tree-sitter";
import { TaskStatus } from "./context/taskList/TaskManager";
import { CodebaseIndexer } from "./indexing/CodebaseIndexer";
import { LLMConfigurationStatuses } from "./llm/constants";
import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
import type { IMessenger } from "./protocol/messenger";

declare global {
interface Window {
Expand Down Expand Up @@ -1066,6 +1069,7 @@ export interface ToolExtras {
contextItems: ContextItem[];
}) => void;
config: ContinueConfig;
messenger?: IMessenger<ToCoreProtocol, FromCoreProtocol>;
codeBaseIndexer?: CodebaseIndexer;
}

Expand Down Expand Up @@ -1800,6 +1804,20 @@ export interface MessageOption {
precompiled: boolean;
}

// Task Manager

type TaskMetadata = {
createdAt: string;
updatedAt: string;
};

export type TaskInfo = {
task_id: string;
name: string;
description: string;
status: TaskStatus;
metadata: TaskMetadata;
};
/* LSP-specific interfaces. */

// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind.
Expand Down
2 changes: 2 additions & 0 deletions core/protocol/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
SiteIndexingConfig,
SlashCommandDescWithSource,
StreamDiffLinesPayload,
TaskInfo,
ToolCall,
} from "../";
import { AutocompleteCodeSnippet } from "../autocomplete/snippets/types";
Expand Down Expand Up @@ -265,4 +266,5 @@ export type ToCoreFromIdeOrWebviewProtocol = {
"process/markAsBackgrounded": [{ toolCallId: string }, void];
"process/isBackgrounded": [{ toolCallId: string }, boolean];
"mdm/setLicenseKey": [{ licenseKey: string }, boolean];
"taskList/list": [undefined, TaskInfo[]];
};
3 changes: 3 additions & 0 deletions core/protocol/passThrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
"isItemTooBig",
"process/markAsBackgrounded",
"process/isBackgrounded",
"controlPlane/getFreeTrialStatus",
"taskList/list",
];

// Message types to pass through from core to webview
Expand All @@ -93,4 +95,5 @@ export const CORE_TO_WEBVIEW_PASS_THROUGH: (keyof ToWebviewFromCoreProtocol)[] =
"didCloseFiles",
"toolCallPartialOutput",
"freeTrialExceeded",
"taskEvent",
];
2 changes: 2 additions & 0 deletions core/protocol/webview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ConfigResult } from "@continuedev/config-yaml";
import { SerializedOrgWithProfiles } from "../config/ProfileLifecycleManager.js";
import { TaskEvent } from "../context/taskList/TaskManager.js";
import { ControlPlaneSessionInfo } from "../control-plane/AuthTypes.js";
import type {
BrowserSerializedContinueConfig,
Expand Down Expand Up @@ -44,4 +45,5 @@ export type ToWebviewFromIdeOrCoreProtocol = {
sessionUpdate: [{ sessionInfo: ControlPlaneSessionInfo | undefined }, void];
toolCallPartialOutput: [{ toolCallId: string; contextItems: any[] }, void];
freeTrialExceeded: [undefined, void];
taskEvent: [TaskEvent, void];
};
1 change: 1 addition & 0 deletions core/tools/builtIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum BuiltInToolNames {
RequestRule = "request_rule",
FetchUrlContent = "fetch_url_content",
CodebaseTool = "codebase",
TaskList = "task_list",

// excluded from allTools for now
ViewRepoMap = "view_repo_map",
Expand Down
3 changes: 3 additions & 0 deletions core/tools/callTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { readFileImpl } from "./implementations/readFile";
import { requestRuleImpl } from "./implementations/requestRule";
import { runTerminalCommandImpl } from "./implementations/runTerminalCommand";
import { searchWebImpl } from "./implementations/searchWeb";
import { taskListImpl } from "./implementations/taskList";
import { viewDiffImpl } from "./implementations/viewDiff";
import { viewRepoMapImpl } from "./implementations/viewRepoMap";
import { viewSubdirectoryImpl } from "./implementations/viewSubdirectory";
Expand Down Expand Up @@ -169,6 +170,8 @@ export async function callBuiltInTool(
return await requestRuleImpl(args, extras);
case BuiltInToolNames.CodebaseTool:
return await codebaseToolImpl(args, extras);
case BuiltInToolNames.TaskList:
return await taskListImpl(args, extras);
case BuiltInToolNames.ViewRepoMap:
return await viewRepoMapImpl(args, extras);
case BuiltInToolNames.ViewSubdirectory:
Expand Down
80 changes: 80 additions & 0 deletions core/tools/definitions/taskListTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Tool } from "../..";
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";

export const taskListTool: Tool = {
type: "function",
displayTitle: "Task List Manager",
wouldLikeTo: "manage tasks",
isCurrently: "managing tasks",
hasAlready: "managed tasks",
readonly: false,
isInstant: false,
group: BUILT_IN_GROUP_NAME,
function: {
name: BuiltInToolNames.TaskList,
description: `A comprehensive task management tool for organizing, tracking, and executing work in a structured queue-based system.
This tool helps manage complex workflows by breaking them down into manageable tasks that can be tracked and executed systematically.

When to use this tool:
- Breaking down complex multi-step work into organized tasks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider making this a bit more concise, this is a lot of tokens for a tool description

- Planning and tracking progress on development projects
- Managing sequences of related changes across a codebase
- Coordinating multiple tasks that need to be completed in order
- Organizing work that benefits from structured planning and execution
- Tracking completion status of various development activities
- Managing task dependencies and workflow organization

Key features:
- Add new tasks with detailed descriptions and context
- List all current tasks with their status and details
- Update existing tasks with new information or status changes
- Start/execute tasks by marking them in progress

Task management workflow:
1. Use 'add' to create new tasks with clear names and detailed descriptions
2. Use 'list' to view all current tasks and their status
3. Use 'runTask' to begin working on a specific task
4. Use 'update' to modify task details

Best practices:
- Create tasks that represent meaningful units of work
- Use descriptive names that clearly indicate the task purpose
- Include detailed descriptions with context, requirements, and acceptance criteria
- Start tasks manually one by one rather than automatic queue processing

Parameters explained:
- action: The specific operation to perform (add, list, update, run_task)
- name: Short, descriptive task name (required for add/update operations)
- description: Detailed task description with context and requirements (required for add/update)
- task_id: Unique identifier for the task (required for update/remove/run_task operations)

The tool automatically handles task ID generation, status tracking, and GUI updates.
Task IDs are managed internally and should not be exposed to users in normal interactions.`,
parameters: {
type: "object",
required: ["action"],
properties: {
action: {
type: "string",
enum: ["add", "list", "update", "run_task"],
description: "The specific action to perform on the task list.",
},
name: {
type: "string",
description:
"A short, descriptive name for the task. Required when adding or updating a task. Should clearly indicate the task purpose.",
},
description: {
type: "string",
description:
"A detailed description of the task including context, requirements, and acceptance criteria. Required when adding or updating a task.",
},
task_id: {
type: "string",
description:
"The unique identifier of the task to operate on. Required when updating or running a specific task. Should not be exposed to users.",
},
},
},
},
};
Loading
Loading