Skip to content

[WIP] feat: task lists #6593

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

Closed
wants to merge 7 commits into from
Closed
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
153 changes: 153 additions & 0 deletions core/context/taskList/TaskManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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",
Running = "running",
Completed = "completed",
}

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

export class TaskManager {
private queue: TaskInfo["id"][] = [];
private taskMap = new Map<TaskInfo["id"], TaskInfo>();
private previousTaskId: TaskInfo["id"] | null = null;

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

private emitEvent(eventType: TaskEvent["type"]): void {
// TODO: messenger.send is null - need to figure out the reason
// this.messenger.send("taskEvent", {
// type: eventType,
// tasks: this.list(),
// });
}

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

this.emitEvent("add");

return taskId;
}

update(taskId: TaskInfo["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["id"]) {
const task = this.taskMap.get(taskId);
if (!task) {
return;
}

this.taskMap.delete(taskId);
this.queue = this.queue.filter((id) => id !== taskId);

this.emitEvent("remove");
}

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

setTaskStatus(taskId: TaskInfo["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["id"]) {
if (!this.taskMap.has(taskId)) {
throw new Error(`Task with id "${taskId}" not found`);
}
return this.taskMap.get(taskId)!;
}

next() {
if (this.previousTaskId) {
const previousTask = this.taskMap.get(this.previousTaskId);
if (previousTask) {
const updatedPreviousTask: TaskInfo = {
...previousTask,
status: TaskStatus.Completed,
};
this.taskMap.set(this.previousTaskId, updatedPreviousTask);

this.emitEvent("update");
}
}

if (this.queue.length === 0) {
return null;
}

const currentTaskId = this.queue.shift()!;
const currentTask = this.taskMap.get(currentTaskId)!;
const updatedCurrentTask: TaskInfo = {
...currentTask,
status: TaskStatus.Running,
metadata: {
...currentTask.metadata,
updatedAt: new Date().toISOString(),
},
};

this.taskMap.set(currentTaskId, updatedCurrentTask);
this.previousTaskId = currentTaskId;

this.emitEvent("update");

return updatedCurrentTask;
}

// TODO
// start(taskId: TaskInfo["id"]) {
// this.updatePreviousTask(taskId, TaskStatus.Pending); // this
// this.taskMap.set(taskId, {
// ...this.taskMap.get(taskId)!,
// status: TaskStatus.Running,
// });
// }
}
45 changes: 45 additions & 0 deletions core/context/taskList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Session, TaskInfo } from "../..";
import type { FromCoreProtocol, ToCoreProtocol } from "../../protocol";
import type { IMessenger } from "../../protocol/messenger";
import { TaskManager } from "./TaskManager";

const taskManagers = new Map<Session["sessionId"], TaskManager>();

export function getTaskManagerForSession(
sessionId: Session["sessionId"],
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
if (taskManagers.has(sessionId)) {
return taskManagers.get(sessionId)!;
}
const newManager = new TaskManager(messenger);
taskManagers.set(sessionId, newManager);
return newManager;
}

export function fetchTaskList(
sessionId: Session["sessionId"],
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
return getTaskManagerForSession(sessionId, messenger).list();
}

export function updateTaskInTaskList(
sessionId: Session["sessionId"],
task: Pick<TaskInfo, "id" | "name" | "description">,
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
getTaskManagerForSession(sessionId, messenger).update(
task.id,
task.name,
task.description,
);
}

export function deleteTaskFromTaskList(
sessionId: Session["sessionId"],
taskId: TaskInfo["id"],
messenger: IMessenger<ToCoreProtocol, FromCoreProtocol>,
) {
getTaskManagerForSession(sessionId, messenger).remove(taskId);
}
18 changes: 18 additions & 0 deletions core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ import {
} from "./config/onboarding";
import { createNewWorkspaceBlockFile } from "./config/workspace/workspaceBlocks";
import { MCPManagerSingleton } from "./context/mcp/MCPManagerSingleton";
import {
deleteTaskFromTaskList,
fetchTaskList,
updateTaskInTaskList,
} from "./context/taskList";
import { setMdmLicenseKey } from "./control-plane/mdm/mdm";
import { ApplyAbortManager } from "./edit/applyAbortManager";
import { streamDiffLines } from "./edit/streamDiffLines";
Expand Down Expand Up @@ -929,6 +934,18 @@ export class Core {
const isValid = setMdmLicenseKey(licenseKey);
return isValid;
});

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

on("taskList/update", ({ data }) => {
updateTaskInTaskList(data.sessionId, data.task, this.messenger);
});

on("taskList/remove", ({ data }) => {
deleteTaskFromTaskList(data.sessionId, data.taskId, this.messenger);
});
}

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

return result;
Expand Down
19 changes: 19 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 @@ -1058,6 +1061,7 @@ export interface ToolExtras {
contextItems: ContextItem[];
}) => void;
config: ContinueConfig;
messenger?: IMessenger<ToCoreProtocol, FromCoreProtocol>;
codeBaseIndexer?: CodebaseIndexer;
}

Expand Down Expand Up @@ -1721,3 +1725,18 @@ export interface CompiledMessagesResult {
export interface MessageOption {
precompiled: boolean;
}

// Task Manager

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

export type TaskInfo = {
id: string;
name: string;
description: string;
status: TaskStatus;
metadata: TaskMetadata;
};
13 changes: 13 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 @@ -258,4 +259,16 @@ export type ToCoreFromIdeOrWebviewProtocol = {
"process/markAsBackgrounded": [{ toolCallId: string }, void];
"process/isBackgrounded": [{ toolCallId: string }, boolean];
"mdm/setLicenseKey": [{ licenseKey: string }, boolean];
"taskList/list": [{ sessionId: Session["sessionId"] }, TaskInfo[]];
"taskList/update": [
{
sessionId: Session["sessionId"];
task: Pick<TaskInfo, "id" | "name" | "description">;
},
void,
];
"taskList/remove": [
{ sessionId: Session["sessionId"]; taskId: TaskInfo["id"] },
void,
];
};
3 changes: 3 additions & 0 deletions core/protocol/passThrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
"process/markAsBackgrounded",
"process/isBackgrounded",
"controlPlane/getFreeTrialStatus",
"taskList/list",
"taskList/update",
"taskList/remove",
];

// Message types to pass through from core to webview
Expand Down
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
52 changes: 52 additions & 0 deletions core/tools/definitions/taskListTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Tool } from "../..";
import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";

export const taskListTool: Tool = {
type: "function",
displayTitle: "Task List Manager",
wouldLikeTo: "manage tasks in the queue",
isCurrently: "managing tasks",
hasAlready: "managed tasks",
readonly: false,
isInstant: false,
group: BUILT_IN_GROUP_NAME,
function: {
name: BuiltInToolNames.TaskList,
description:
'Manage and execute tasks in a queue-based system. Supports adding, updating, removing, and monitoring tasks. When you perform "runAllTasks" action, you should keep executing the tasks until hasNextTask is false. Before executing any task, set the task to in progress by using the "runTask" action.',
parameters: {
type: "object",
required: ["action"],
properties: {
action: {
type: "string",
enum: [
"add",
"update",
"remove",
"list",
"runTask",
"runAllTasks",
"completeTask",
],
description: "The action to perform on the task.",
},
name: {
type: "string",
description:
"A short useful name for the task. Required when adding or updating a task",
},
description: {
type: "string",
description:
"A detailed description of the task. Required when adding or updating a task",
},
taskId: {
type: "string",
description:
"Required when running, updating, completing or removing a specific task",
},
},
},
},
};
Loading
Loading