@finityno/claude-code-acp 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-agent.d.ts +3 -0
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +42 -1
- package/dist/lib.d.ts +2 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +4 -1
- package/dist/mcp-server.d.ts +2 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +9 -2
- package/dist/task-store.d.ts +123 -0
- package/dist/task-store.d.ts.map +1 -0
- package/dist/task-store.js +292 -0
- package/dist/work-item-mcp-tools.d.ts +12 -0
- package/dist/work-item-mcp-tools.d.ts.map +1 -0
- package/dist/work-item-mcp-tools.js +296 -0
- package/package.json +1 -1
package/dist/acp-agent.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { SettingsManager } from "./settings.js";
|
|
|
3
3
|
import { CanUseTool, Options, PermissionMode, Query, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import { Pushable } from "./utils.js";
|
|
5
5
|
import { SubagentTracker } from "./subagent-tracker.js";
|
|
6
|
+
import { TaskStore } from "./task-store.js";
|
|
6
7
|
import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
|
|
7
8
|
import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
|
|
8
9
|
export declare const CLAUDE_CONFIG_DIR: string;
|
|
@@ -83,6 +84,8 @@ export declare class ClaudeAcpAgent implements Agent {
|
|
|
83
84
|
clientCapabilities?: ClientCapabilities;
|
|
84
85
|
logger: Logger;
|
|
85
86
|
subagentTracker: SubagentTracker;
|
|
87
|
+
taskStore: TaskStore;
|
|
88
|
+
taskListId: string;
|
|
86
89
|
constructor(client: AgentSideConnection, logger?: Logger);
|
|
87
90
|
initialize(request: InitializeRequest): Promise<InitializeResponse>;
|
|
88
91
|
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
package/dist/acp-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-agent.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createMcpServer } from "./mcp-server.js";
|
|
|
9
9
|
import { EDIT_TOOL_NAMES, acpToolNames } from "./tools.js";
|
|
10
10
|
import { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, registerHookCallback, createPostToolUseHook, createPreToolUseHook, } from "./tools.js";
|
|
11
11
|
import { SubagentTracker, isTaskToolInput, } from "./subagent-tracker.js";
|
|
12
|
+
import { TaskStore } from "./task-store.js";
|
|
12
13
|
import packageJson from "../package.json" with { type: "json" };
|
|
13
14
|
import { randomUUID } from "node:crypto";
|
|
14
15
|
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE ?? path.join(os.homedir(), ".claude");
|
|
@@ -23,6 +24,37 @@ export class ClaudeAcpAgent {
|
|
|
23
24
|
this.toolUseCache = {};
|
|
24
25
|
this.logger = logger ?? console;
|
|
25
26
|
this.subagentTracker = new SubagentTracker(client, this.logger);
|
|
27
|
+
// Initialize TaskStore (use provided task list ID or auto-generate one)
|
|
28
|
+
const taskListId = process.env.CLAUDE_CODE_TASK_LIST_ID || crypto.randomUUID();
|
|
29
|
+
this.taskListId = taskListId;
|
|
30
|
+
this.taskStore = new TaskStore({
|
|
31
|
+
taskListId,
|
|
32
|
+
logger: this.logger,
|
|
33
|
+
onChange: (tasks) => {
|
|
34
|
+
// Broadcast task updates to all sessions
|
|
35
|
+
for (const sessionId of Object.keys(this.sessions)) {
|
|
36
|
+
this.client.sessionUpdate({
|
|
37
|
+
sessionId,
|
|
38
|
+
update: {
|
|
39
|
+
sessionUpdate: "tool_call_update",
|
|
40
|
+
toolCallId: "task-list-update",
|
|
41
|
+
_meta: {
|
|
42
|
+
claudeCode: {
|
|
43
|
+
taskListUpdate: {
|
|
44
|
+
taskListId,
|
|
45
|
+
tasks,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// Initialize and start watching for changes
|
|
55
|
+
this.taskStore.init().then(() => {
|
|
56
|
+
this.taskStore.watch();
|
|
57
|
+
});
|
|
26
58
|
}
|
|
27
59
|
async initialize(request) {
|
|
28
60
|
this.clientCapabilities = request.clientCapabilities;
|
|
@@ -583,7 +615,7 @@ export class ClaudeAcpAgent {
|
|
|
583
615
|
}
|
|
584
616
|
// Only add the acp MCP server if built-in tools are not disabled
|
|
585
617
|
if (!params._meta?.disableBuiltInTools) {
|
|
586
|
-
const server = createMcpServer(this, sessionId, this.clientCapabilities);
|
|
618
|
+
const server = createMcpServer(this, sessionId, this.clientCapabilities, this.taskStore);
|
|
587
619
|
mcpServers["acp"] = {
|
|
588
620
|
type: "sdk",
|
|
589
621
|
name: "acp",
|
|
@@ -614,12 +646,21 @@ export class ClaudeAcpAgent {
|
|
|
614
646
|
const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
|
|
615
647
|
? parseInt(process.env.MAX_THINKING_TOKENS, 10)
|
|
616
648
|
: undefined;
|
|
649
|
+
// Build env with task list ID if configured
|
|
650
|
+
const sdkEnv = {
|
|
651
|
+
...userProvidedOptions?.env,
|
|
652
|
+
};
|
|
653
|
+
if (this.taskListId) {
|
|
654
|
+
sdkEnv.CLAUDE_CODE_TASK_LIST_ID = this.taskListId;
|
|
655
|
+
}
|
|
617
656
|
const options = {
|
|
618
657
|
systemPrompt,
|
|
619
658
|
settingSources: ["user", "project", "local"],
|
|
620
659
|
stderr: (err) => this.logger.error(err),
|
|
621
660
|
...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
|
|
622
661
|
...userProvidedOptions,
|
|
662
|
+
// Pass task list ID to SDK so Claude's internal tools share our task list
|
|
663
|
+
...(Object.keys(sdkEnv).length > 0 && { env: sdkEnv }),
|
|
623
664
|
// Override certain fields that must be controlled by ACP
|
|
624
665
|
cwd: params.cwd,
|
|
625
666
|
includePartialMessages: true,
|
package/dist/lib.d.ts
CHANGED
|
@@ -6,5 +6,7 @@ export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type
|
|
|
6
6
|
export { SubagentTracker, isTaskToolInput, extractSubagentMeta, type TrackedSubagent, type SubagentStatus, type SubagentType, type SubagentEventType, type SubagentUpdateMeta, type SubagentEventListener, type SubagentStats, type TaskToolInput, type SerializedTrackerState, type SerializedTask, type SDKTaskNotification, } from "./subagent-tracker.js";
|
|
7
7
|
export { TaskManager, type TaskManagerOptions, type TaskFilter, } from "./task-manager.js";
|
|
8
8
|
export { registerTaskMcpTools, type TaskMcpToolsOptions } from "./task-mcp-tools.js";
|
|
9
|
+
export { TaskStore, type Task, type TaskStatus, type TaskCreateInput, type TaskUpdateInput, type TaskStoreOptions, } from "./task-store.js";
|
|
10
|
+
export { registerWorkItemMcpTools, type WorkItemMcpToolsOptions, } from "./work-item-mcp-tools.js";
|
|
9
11
|
export type { ClaudePlanEntry } from "./tools.js";
|
|
10
12
|
//# sourceMappingURL=lib.d.ts.map
|
package/dist/lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,EACzB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAGrF,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,EACzB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAGrF,OAAO,EACL,SAAS,EACT,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,wBAAwB,EACxB,KAAK,uBAAuB,GAC7B,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/lib.js
CHANGED
|
@@ -8,5 +8,8 @@ export { SettingsManager, } from "./settings.js";
|
|
|
8
8
|
export { SubagentTracker, isTaskToolInput, extractSubagentMeta, } from "./subagent-tracker.js";
|
|
9
9
|
// Export task management
|
|
10
10
|
export { TaskManager, } from "./task-manager.js";
|
|
11
|
-
// Export
|
|
11
|
+
// Export subagent MCP tools registration (tracks Task tool spawning)
|
|
12
12
|
export { registerTaskMcpTools } from "./task-mcp-tools.js";
|
|
13
|
+
// Export work item task store and tools (TaskCreate, TaskGet, TaskUpdate, TaskList)
|
|
14
|
+
export { TaskStore, } from "./task-store.js";
|
|
15
|
+
export { registerWorkItemMcpTools, } from "./work-item-mcp-tools.js";
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { ClaudeAcpAgent } from "./acp-agent.js";
|
|
3
3
|
import { ClientCapabilities } from "@agentclientprotocol/sdk";
|
|
4
|
+
import { TaskStore } from "./task-store.js";
|
|
4
5
|
export declare const SYSTEM_REMINDER = "\n\n<system-reminder>\nWhenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.\n</system-reminder>";
|
|
5
|
-
export declare function createMcpServer(agent: ClaudeAcpAgent, sessionId: string, clientCapabilities: ClientCapabilities | undefined): McpServer;
|
|
6
|
+
export declare function createMcpServer(agent: ClaudeAcpAgent, sessionId: string, clientCapabilities: ClientCapabilities | undefined, taskStore?: TaskStore): McpServer;
|
|
6
7
|
/**
|
|
7
8
|
* Replace text in a file and calculate the line numbers where the edits occurred.
|
|
8
9
|
*
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,OAAO,EAAqB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AASlC,eAAO,MAAM,eAAe,iSAIT,CAAC;AA2BpB,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,OAAO,EAAqB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AASlC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,eAAO,MAAM,eAAe,iSAIT,CAAC;AA2BpB,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,EAClD,SAAS,CAAC,EAAE,SAAS,GACpB,SAAS,CAwoBX;AA+DD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,GACD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAyF/C"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -58,6 +58,7 @@ import * as path from "node:path";
|
|
|
58
58
|
import * as fs from "node:fs/promises";
|
|
59
59
|
import { sleep, unreachable, extractLinesWithByteLimit } from "./utils.js";
|
|
60
60
|
import { registerTaskMcpTools } from "./task-mcp-tools.js";
|
|
61
|
+
import { registerWorkItemMcpTools } from "./work-item-mcp-tools.js";
|
|
61
62
|
import { acpToolNames } from "./tools.js";
|
|
62
63
|
export const SYSTEM_REMINDER = `
|
|
63
64
|
|
|
@@ -87,7 +88,7 @@ const unqualifiedToolNames = {
|
|
|
87
88
|
killShell: "KillShell",
|
|
88
89
|
bashOutput: "BashOutput",
|
|
89
90
|
};
|
|
90
|
-
export function createMcpServer(agent, sessionId, clientCapabilities) {
|
|
91
|
+
export function createMcpServer(agent, sessionId, clientCapabilities, taskStore) {
|
|
91
92
|
/**
|
|
92
93
|
* This checks if a given path is related to internal agent persistence and if the agent should be allowed to read/write from here.
|
|
93
94
|
* We let the agent do normal fs operations on these paths so that it can persist its state.
|
|
@@ -650,11 +651,17 @@ In sessions with ${acpToolNames.killShell} always use it instead of KillShell.`,
|
|
|
650
651
|
}
|
|
651
652
|
});
|
|
652
653
|
}
|
|
653
|
-
// Register
|
|
654
|
+
// Register subagent tracking tools (for Task tool spawning)
|
|
654
655
|
registerTaskMcpTools(server, {
|
|
655
656
|
tracker: agent.subagentTracker,
|
|
656
657
|
sessionId,
|
|
657
658
|
});
|
|
659
|
+
// Register work item task tools (TaskCreate, TaskGet, TaskUpdate, TaskList)
|
|
660
|
+
if (taskStore) {
|
|
661
|
+
registerWorkItemMcpTools(server, {
|
|
662
|
+
taskStore,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
658
665
|
return server;
|
|
659
666
|
}
|
|
660
667
|
function stripCommonPrefix(a, b) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Logger } from "./acp-agent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Task status matching Claude Code's internal system
|
|
4
|
+
*/
|
|
5
|
+
export type TaskStatus = "pending" | "in_progress" | "completed";
|
|
6
|
+
/**
|
|
7
|
+
* Task structure matching Claude Code's ~/.claude/tasks/ format
|
|
8
|
+
*/
|
|
9
|
+
export interface Task {
|
|
10
|
+
id: string;
|
|
11
|
+
subject: string;
|
|
12
|
+
description: string;
|
|
13
|
+
activeForm: string;
|
|
14
|
+
status: TaskStatus;
|
|
15
|
+
owner?: string;
|
|
16
|
+
blocks: string[];
|
|
17
|
+
blockedBy: string[];
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Input for creating a new task
|
|
22
|
+
*/
|
|
23
|
+
export interface TaskCreateInput {
|
|
24
|
+
subject: string;
|
|
25
|
+
description: string;
|
|
26
|
+
activeForm?: string;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Input for updating a task
|
|
31
|
+
*/
|
|
32
|
+
export interface TaskUpdateInput {
|
|
33
|
+
status?: TaskStatus;
|
|
34
|
+
subject?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
activeForm?: string;
|
|
37
|
+
owner?: string;
|
|
38
|
+
addBlocks?: string[];
|
|
39
|
+
addBlockedBy?: string[];
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for TaskStore
|
|
44
|
+
*/
|
|
45
|
+
export interface TaskStoreOptions {
|
|
46
|
+
/** Task list ID (UUID or custom ID) */
|
|
47
|
+
taskListId: string;
|
|
48
|
+
/** Base path for task storage (default: ~/.claude/tasks) */
|
|
49
|
+
basePath?: string;
|
|
50
|
+
/** Logger instance */
|
|
51
|
+
logger?: Logger;
|
|
52
|
+
/** Callback when tasks change (from file watcher) */
|
|
53
|
+
onChange?: (tasks: Task[]) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* TaskStore manages reading/writing tasks to ~/.claude/tasks/
|
|
57
|
+
* Compatible with Claude Code's internal task system
|
|
58
|
+
*/
|
|
59
|
+
export declare class TaskStore {
|
|
60
|
+
private taskListId;
|
|
61
|
+
private basePath;
|
|
62
|
+
private logger;
|
|
63
|
+
private onChange?;
|
|
64
|
+
private watcher;
|
|
65
|
+
private nextId;
|
|
66
|
+
private initialized;
|
|
67
|
+
constructor(options: TaskStoreOptions);
|
|
68
|
+
/**
|
|
69
|
+
* Get the directory path for this task list
|
|
70
|
+
*/
|
|
71
|
+
get taskListPath(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Initialize the task store (create directory, determine next ID)
|
|
74
|
+
*/
|
|
75
|
+
init(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Create a new task
|
|
78
|
+
*/
|
|
79
|
+
create(input: TaskCreateInput): Promise<Task>;
|
|
80
|
+
/**
|
|
81
|
+
* Get a task by ID
|
|
82
|
+
*/
|
|
83
|
+
get(taskId: string): Promise<Task | null>;
|
|
84
|
+
/**
|
|
85
|
+
* Update a task
|
|
86
|
+
*/
|
|
87
|
+
update(taskId: string, input: TaskUpdateInput): Promise<Task>;
|
|
88
|
+
/**
|
|
89
|
+
* List all tasks
|
|
90
|
+
*/
|
|
91
|
+
list(): Promise<Task[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Delete a task
|
|
94
|
+
*/
|
|
95
|
+
delete(taskId: string): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Start watching for changes from other sessions
|
|
98
|
+
*/
|
|
99
|
+
watch(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Stop watching for changes
|
|
102
|
+
*/
|
|
103
|
+
close(): void;
|
|
104
|
+
/**
|
|
105
|
+
* Get task statistics
|
|
106
|
+
*/
|
|
107
|
+
getStats(): Promise<{
|
|
108
|
+
total: number;
|
|
109
|
+
pending: number;
|
|
110
|
+
inProgress: number;
|
|
111
|
+
completed: number;
|
|
112
|
+
blocked: number;
|
|
113
|
+
}>;
|
|
114
|
+
private getTaskFilePath;
|
|
115
|
+
private saveTask;
|
|
116
|
+
/**
|
|
117
|
+
* Generate activeForm from subject (convert to present participle)
|
|
118
|
+
* "Fix bug" -> "Fixing bug"
|
|
119
|
+
* "Add feature" -> "Adding feature"
|
|
120
|
+
*/
|
|
121
|
+
private generateActiveForm;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=task-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, readdir, rm } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
/**
|
|
5
|
+
* TaskStore manages reading/writing tasks to ~/.claude/tasks/
|
|
6
|
+
* Compatible with Claude Code's internal task system
|
|
7
|
+
*/
|
|
8
|
+
export class TaskStore {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.watcher = null;
|
|
11
|
+
this.nextId = 1;
|
|
12
|
+
this.initialized = false;
|
|
13
|
+
this.taskListId = options.taskListId;
|
|
14
|
+
this.basePath = options.basePath ?? join(homedir(), ".claude", "tasks");
|
|
15
|
+
this.logger = options.logger ?? console;
|
|
16
|
+
this.onChange = options.onChange;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get the directory path for this task list
|
|
20
|
+
*/
|
|
21
|
+
get taskListPath() {
|
|
22
|
+
return join(this.basePath, this.taskListId);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the task store (create directory, determine next ID)
|
|
26
|
+
*/
|
|
27
|
+
async init() {
|
|
28
|
+
if (this.initialized)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
await mkdir(this.taskListPath, { recursive: true });
|
|
32
|
+
// Determine next ID from existing tasks
|
|
33
|
+
const tasks = await this.list();
|
|
34
|
+
if (tasks.length > 0) {
|
|
35
|
+
const maxId = Math.max(...tasks.map((t) => parseInt(t.id, 10) || 0));
|
|
36
|
+
this.nextId = maxId + 1;
|
|
37
|
+
}
|
|
38
|
+
this.initialized = true;
|
|
39
|
+
this.logger.log(`[TaskStore] Initialized at ${this.taskListPath}, next ID: ${this.nextId}`);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.logger.error(`[TaskStore] Failed to initialize:`, err);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a new task
|
|
48
|
+
*/
|
|
49
|
+
async create(input) {
|
|
50
|
+
await this.init();
|
|
51
|
+
const task = {
|
|
52
|
+
id: String(this.nextId++),
|
|
53
|
+
subject: input.subject,
|
|
54
|
+
description: input.description,
|
|
55
|
+
activeForm: input.activeForm ?? this.generateActiveForm(input.subject),
|
|
56
|
+
status: "pending",
|
|
57
|
+
blocks: [],
|
|
58
|
+
blockedBy: [],
|
|
59
|
+
metadata: input.metadata,
|
|
60
|
+
};
|
|
61
|
+
await this.saveTask(task);
|
|
62
|
+
this.logger.log(`[TaskStore] Created task ${task.id}: ${task.subject}`);
|
|
63
|
+
return task;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get a task by ID
|
|
67
|
+
*/
|
|
68
|
+
async get(taskId) {
|
|
69
|
+
await this.init();
|
|
70
|
+
const filePath = this.getTaskFilePath(taskId);
|
|
71
|
+
try {
|
|
72
|
+
const content = await readFile(filePath, "utf8");
|
|
73
|
+
return JSON.parse(content);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err.code === "ENOENT") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Update a task
|
|
84
|
+
*/
|
|
85
|
+
async update(taskId, input) {
|
|
86
|
+
await this.init();
|
|
87
|
+
const task = await this.get(taskId);
|
|
88
|
+
if (!task) {
|
|
89
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
90
|
+
}
|
|
91
|
+
// Apply updates
|
|
92
|
+
if (input.status !== undefined)
|
|
93
|
+
task.status = input.status;
|
|
94
|
+
if (input.subject !== undefined)
|
|
95
|
+
task.subject = input.subject;
|
|
96
|
+
if (input.description !== undefined)
|
|
97
|
+
task.description = input.description;
|
|
98
|
+
if (input.activeForm !== undefined)
|
|
99
|
+
task.activeForm = input.activeForm;
|
|
100
|
+
if (input.owner !== undefined)
|
|
101
|
+
task.owner = input.owner;
|
|
102
|
+
// Handle blocks/blockedBy additions
|
|
103
|
+
if (input.addBlocks) {
|
|
104
|
+
for (const blockId of input.addBlocks) {
|
|
105
|
+
if (!task.blocks.includes(blockId)) {
|
|
106
|
+
task.blocks.push(blockId);
|
|
107
|
+
}
|
|
108
|
+
// Also update the blocked task's blockedBy
|
|
109
|
+
const blockedTask = await this.get(blockId);
|
|
110
|
+
if (blockedTask && !blockedTask.blockedBy.includes(taskId)) {
|
|
111
|
+
blockedTask.blockedBy.push(taskId);
|
|
112
|
+
await this.saveTask(blockedTask);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (input.addBlockedBy) {
|
|
117
|
+
for (const blockerId of input.addBlockedBy) {
|
|
118
|
+
if (!task.blockedBy.includes(blockerId)) {
|
|
119
|
+
task.blockedBy.push(blockerId);
|
|
120
|
+
}
|
|
121
|
+
// Also update the blocking task's blocks
|
|
122
|
+
const blockerTask = await this.get(blockerId);
|
|
123
|
+
if (blockerTask && !blockerTask.blocks.includes(taskId)) {
|
|
124
|
+
blockerTask.blocks.push(taskId);
|
|
125
|
+
await this.saveTask(blockerTask);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Merge metadata
|
|
130
|
+
if (input.metadata) {
|
|
131
|
+
task.metadata = { ...task.metadata, ...input.metadata };
|
|
132
|
+
// Remove null values (deletion)
|
|
133
|
+
for (const [key, value] of Object.entries(input.metadata)) {
|
|
134
|
+
if (value === null && task.metadata) {
|
|
135
|
+
delete task.metadata[key];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// When completing a task, update blockedBy for tasks it blocks
|
|
140
|
+
if (input.status === "completed") {
|
|
141
|
+
for (const blockedId of task.blocks) {
|
|
142
|
+
const blockedTask = await this.get(blockedId);
|
|
143
|
+
if (blockedTask) {
|
|
144
|
+
blockedTask.blockedBy = blockedTask.blockedBy.filter((id) => id !== taskId);
|
|
145
|
+
await this.saveTask(blockedTask);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
await this.saveTask(task);
|
|
150
|
+
this.logger.log(`[TaskStore] Updated task ${taskId}: ${task.subject} -> ${task.status}`);
|
|
151
|
+
return task;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* List all tasks
|
|
155
|
+
*/
|
|
156
|
+
async list() {
|
|
157
|
+
await this.init();
|
|
158
|
+
try {
|
|
159
|
+
const files = await readdir(this.taskListPath);
|
|
160
|
+
const tasks = [];
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (!file.endsWith(".json"))
|
|
163
|
+
continue;
|
|
164
|
+
const taskId = file.replace(".json", "");
|
|
165
|
+
const task = await this.get(taskId);
|
|
166
|
+
if (task)
|
|
167
|
+
tasks.push(task);
|
|
168
|
+
}
|
|
169
|
+
// Sort by ID (numeric)
|
|
170
|
+
return tasks.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10));
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
if (err.code === "ENOENT") {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Delete a task
|
|
181
|
+
*/
|
|
182
|
+
async delete(taskId) {
|
|
183
|
+
await this.init();
|
|
184
|
+
const task = await this.get(taskId);
|
|
185
|
+
if (!task)
|
|
186
|
+
return false;
|
|
187
|
+
// Remove from blocks/blockedBy of other tasks
|
|
188
|
+
for (const blockedId of task.blocks) {
|
|
189
|
+
const blockedTask = await this.get(blockedId);
|
|
190
|
+
if (blockedTask) {
|
|
191
|
+
blockedTask.blockedBy = blockedTask.blockedBy.filter((id) => id !== taskId);
|
|
192
|
+
await this.saveTask(blockedTask);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const blockerId of task.blockedBy) {
|
|
196
|
+
const blockerTask = await this.get(blockerId);
|
|
197
|
+
if (blockerTask) {
|
|
198
|
+
blockerTask.blocks = blockerTask.blocks.filter((id) => id !== taskId);
|
|
199
|
+
await this.saveTask(blockerTask);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await rm(this.getTaskFilePath(taskId));
|
|
204
|
+
this.logger.log(`[TaskStore] Deleted task ${taskId}`);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Start watching for changes from other sessions
|
|
213
|
+
*/
|
|
214
|
+
async watch() {
|
|
215
|
+
if (this.watcher)
|
|
216
|
+
return;
|
|
217
|
+
await this.init();
|
|
218
|
+
try {
|
|
219
|
+
const fsWatch = await import("fs");
|
|
220
|
+
this.watcher = fsWatch.watch(this.taskListPath, async (eventType, filename) => {
|
|
221
|
+
if (!filename?.endsWith(".json"))
|
|
222
|
+
return;
|
|
223
|
+
this.logger.log(`[TaskStore] File change detected: ${eventType} ${filename}`);
|
|
224
|
+
if (this.onChange) {
|
|
225
|
+
const tasks = await this.list();
|
|
226
|
+
this.onChange(tasks);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
this.logger.log(`[TaskStore] Watching for changes at ${this.taskListPath}`);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
this.logger.error(`[TaskStore] Failed to start watcher:`, err);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Stop watching for changes
|
|
237
|
+
*/
|
|
238
|
+
close() {
|
|
239
|
+
if (this.watcher) {
|
|
240
|
+
this.watcher.close();
|
|
241
|
+
this.watcher = null;
|
|
242
|
+
this.logger.log(`[TaskStore] Stopped watching`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get task statistics
|
|
247
|
+
*/
|
|
248
|
+
async getStats() {
|
|
249
|
+
const tasks = await this.list();
|
|
250
|
+
return {
|
|
251
|
+
total: tasks.length,
|
|
252
|
+
pending: tasks.filter((t) => t.status === "pending").length,
|
|
253
|
+
inProgress: tasks.filter((t) => t.status === "in_progress").length,
|
|
254
|
+
completed: tasks.filter((t) => t.status === "completed").length,
|
|
255
|
+
blocked: tasks.filter((t) => t.blockedBy.length > 0 && t.status !== "completed").length,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Private helpers
|
|
259
|
+
getTaskFilePath(taskId) {
|
|
260
|
+
return join(this.taskListPath, `${taskId}.json`);
|
|
261
|
+
}
|
|
262
|
+
async saveTask(task) {
|
|
263
|
+
const filePath = this.getTaskFilePath(task.id);
|
|
264
|
+
await writeFile(filePath, JSON.stringify(task, null, 2), "utf8");
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Generate activeForm from subject (convert to present participle)
|
|
268
|
+
* "Fix bug" -> "Fixing bug"
|
|
269
|
+
* "Add feature" -> "Adding feature"
|
|
270
|
+
*/
|
|
271
|
+
generateActiveForm(subject) {
|
|
272
|
+
const words = subject.split(" ");
|
|
273
|
+
if (words.length === 0)
|
|
274
|
+
return subject;
|
|
275
|
+
const verb = words[0].toLowerCase();
|
|
276
|
+
let participle;
|
|
277
|
+
// Handle common verb endings
|
|
278
|
+
if (verb.endsWith("e")) {
|
|
279
|
+
participle = verb.slice(0, -1) + "ing";
|
|
280
|
+
}
|
|
281
|
+
else if (verb.match(/[aeiou][^aeiou]$/)) {
|
|
282
|
+
// Double consonant for short vowel + consonant
|
|
283
|
+
participle = verb + verb.slice(-1) + "ing";
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
participle = verb + "ing";
|
|
287
|
+
}
|
|
288
|
+
// Capitalize first letter
|
|
289
|
+
participle = participle.charAt(0).toUpperCase() + participle.slice(1);
|
|
290
|
+
return [participle, ...words.slice(1)].join(" ");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { TaskStore } from "./task-store.js";
|
|
3
|
+
export interface WorkItemMcpToolsOptions {
|
|
4
|
+
/** The TaskStore instance for managing work item tasks */
|
|
5
|
+
taskStore: TaskStore;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Register MCP tools for work item task management (TaskCreate, TaskGet, TaskUpdate, TaskList)
|
|
9
|
+
* These match Claude Code's internal task system stored in ~/.claude/tasks/
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerWorkItemMcpTools(server: McpServer, options: WorkItemMcpToolsOptions): void;
|
|
12
|
+
//# sourceMappingURL=work-item-mcp-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-mcp-tools.d.ts","sourceRoot":"","sources":["../src/work-item-mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAoB,MAAM,iBAAiB,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,uBAAuB,GAC/B,IAAI,CAgUN"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Register MCP tools for work item task management (TaskCreate, TaskGet, TaskUpdate, TaskList)
|
|
4
|
+
* These match Claude Code's internal task system stored in ~/.claude/tasks/
|
|
5
|
+
*/
|
|
6
|
+
export function registerWorkItemMcpTools(server, options) {
|
|
7
|
+
const { taskStore } = options;
|
|
8
|
+
// TaskCreate - Create a new task
|
|
9
|
+
server.registerTool("TaskCreate", {
|
|
10
|
+
title: "Create Task",
|
|
11
|
+
description: `Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
12
|
+
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
13
|
+
|
|
14
|
+
## When to Use This Tool
|
|
15
|
+
|
|
16
|
+
Use this tool proactively in these scenarios:
|
|
17
|
+
|
|
18
|
+
- Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
19
|
+
- Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
20
|
+
- User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
21
|
+
- User provides multiple tasks - When users provide a list of things to be done
|
|
22
|
+
- After receiving new instructions - Immediately capture user requirements as tasks
|
|
23
|
+
- When you start working on a task - Mark it as in_progress BEFORE beginning work
|
|
24
|
+
- After completing a task - Mark it as completed
|
|
25
|
+
|
|
26
|
+
## Task Fields
|
|
27
|
+
|
|
28
|
+
- **subject**: A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")
|
|
29
|
+
- **description**: Detailed description of what needs to be done, including context and acceptance criteria
|
|
30
|
+
- **activeForm**: Present continuous form shown in spinner when task is in_progress (e.g., "Fixing authentication bug")
|
|
31
|
+
|
|
32
|
+
**IMPORTANT**: Always provide activeForm when creating tasks. The subject should be imperative ("Run tests") while activeForm should be present continuous ("Running tests").`,
|
|
33
|
+
inputSchema: {
|
|
34
|
+
subject: z.string().describe("A brief title for the task in imperative form"),
|
|
35
|
+
description: z.string().describe("A detailed description of what needs to be done"),
|
|
36
|
+
activeForm: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")'),
|
|
40
|
+
metadata: z
|
|
41
|
+
.record(z.string(), z.unknown())
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Arbitrary metadata to attach to the task"),
|
|
44
|
+
},
|
|
45
|
+
annotations: {
|
|
46
|
+
title: "Create task",
|
|
47
|
+
readOnlyHint: false,
|
|
48
|
+
},
|
|
49
|
+
}, async (input) => {
|
|
50
|
+
try {
|
|
51
|
+
const task = await taskStore.create({
|
|
52
|
+
subject: input.subject,
|
|
53
|
+
description: input.description,
|
|
54
|
+
activeForm: input.activeForm,
|
|
55
|
+
metadata: input.metadata,
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Created task ${task.id}: "${task.subject}"`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
isError: true,
|
|
69
|
+
content: [{ type: "text", text: `Failed to create task: ${err}` }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// TaskGet - Get a task by ID
|
|
74
|
+
server.registerTool("TaskGet", {
|
|
75
|
+
title: "Get Task",
|
|
76
|
+
description: `Use this tool to retrieve a task by its ID from the task list.
|
|
77
|
+
|
|
78
|
+
## When to Use This Tool
|
|
79
|
+
|
|
80
|
+
- When you need the full description and context before starting work on a task
|
|
81
|
+
- To understand task dependencies (what it blocks, what blocks it)
|
|
82
|
+
- After being assigned a task, to get complete requirements
|
|
83
|
+
|
|
84
|
+
## Output
|
|
85
|
+
|
|
86
|
+
Returns full task details:
|
|
87
|
+
- **subject**: Task title
|
|
88
|
+
- **description**: Detailed requirements and context
|
|
89
|
+
- **status**: 'pending', 'in_progress', or 'completed'
|
|
90
|
+
- **blocks**: Tasks waiting on this one to complete
|
|
91
|
+
- **blockedBy**: Tasks that must complete before this one can start
|
|
92
|
+
|
|
93
|
+
## Tips
|
|
94
|
+
|
|
95
|
+
- After fetching a task, verify its blockedBy list is empty before beginning work.
|
|
96
|
+
- Use TaskList to see all tasks in summary form.`,
|
|
97
|
+
inputSchema: {
|
|
98
|
+
taskId: z.string().describe("The ID of the task to retrieve"),
|
|
99
|
+
},
|
|
100
|
+
annotations: {
|
|
101
|
+
title: "Get task",
|
|
102
|
+
readOnlyHint: true,
|
|
103
|
+
},
|
|
104
|
+
}, async (input) => {
|
|
105
|
+
try {
|
|
106
|
+
const task = await taskStore.get(input.taskId);
|
|
107
|
+
if (!task) {
|
|
108
|
+
return {
|
|
109
|
+
isError: true,
|
|
110
|
+
content: [{ type: "text", text: `Task not found: ${input.taskId}` }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify(task, null, 2),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
isError: true,
|
|
125
|
+
content: [{ type: "text", text: `Failed to get task: ${err}` }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// TaskUpdate - Update a task
|
|
130
|
+
server.registerTool("TaskUpdate", {
|
|
131
|
+
title: "Update Task",
|
|
132
|
+
description: `Use this tool to update a task in the task list.
|
|
133
|
+
|
|
134
|
+
## When to Use This Tool
|
|
135
|
+
|
|
136
|
+
**Mark tasks as resolved:**
|
|
137
|
+
- When you have completed the work described in a task
|
|
138
|
+
- When a task is no longer needed or has been superseded
|
|
139
|
+
- IMPORTANT: Always mark your assigned tasks as resolved when you finish them
|
|
140
|
+
- After resolving, call TaskList to find your next task
|
|
141
|
+
|
|
142
|
+
**Update task details:**
|
|
143
|
+
- When requirements change or become clearer
|
|
144
|
+
- When establishing dependencies between tasks
|
|
145
|
+
|
|
146
|
+
## Fields You Can Update
|
|
147
|
+
|
|
148
|
+
- **status**: The task status ('pending', 'in_progress', 'completed')
|
|
149
|
+
- **subject**: Change the task title (imperative form, e.g., "Run tests")
|
|
150
|
+
- **description**: Change the task description
|
|
151
|
+
- **activeForm**: Present continuous form shown when in_progress
|
|
152
|
+
- **owner**: Change the task owner (agent name)
|
|
153
|
+
- **metadata**: Merge metadata keys into the task (set a key to null to delete it)
|
|
154
|
+
- **addBlocks**: Mark tasks that cannot start until this one completes
|
|
155
|
+
- **addBlockedBy**: Mark tasks that must complete before this one can start
|
|
156
|
+
|
|
157
|
+
## Status Workflow
|
|
158
|
+
|
|
159
|
+
Status progresses: \`pending\` → \`in_progress\` → \`completed\`
|
|
160
|
+
|
|
161
|
+
## Examples
|
|
162
|
+
|
|
163
|
+
Mark task as in progress when starting work:
|
|
164
|
+
\`\`\`json
|
|
165
|
+
{"taskId": "1", "status": "in_progress"}
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
Mark task as completed after finishing work:
|
|
169
|
+
\`\`\`json
|
|
170
|
+
{"taskId": "1", "status": "completed"}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
Set up task dependencies:
|
|
174
|
+
\`\`\`json
|
|
175
|
+
{"taskId": "2", "addBlockedBy": ["1"]}
|
|
176
|
+
\`\`\``,
|
|
177
|
+
inputSchema: {
|
|
178
|
+
taskId: z.string().describe("The ID of the task to update"),
|
|
179
|
+
status: z
|
|
180
|
+
.enum(["pending", "in_progress", "completed"])
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("New status for the task"),
|
|
183
|
+
subject: z.string().optional().describe("New subject for the task"),
|
|
184
|
+
description: z.string().optional().describe("New description for the task"),
|
|
185
|
+
activeForm: z
|
|
186
|
+
.string()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")'),
|
|
189
|
+
owner: z.string().optional().describe("New owner for the task"),
|
|
190
|
+
addBlocks: z
|
|
191
|
+
.array(z.string())
|
|
192
|
+
.optional()
|
|
193
|
+
.describe("Task IDs that this task blocks"),
|
|
194
|
+
addBlockedBy: z
|
|
195
|
+
.array(z.string())
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("Task IDs that block this task"),
|
|
198
|
+
metadata: z
|
|
199
|
+
.record(z.string(), z.unknown())
|
|
200
|
+
.optional()
|
|
201
|
+
.describe("Metadata keys to merge into the task. Set a key to null to delete it."),
|
|
202
|
+
},
|
|
203
|
+
annotations: {
|
|
204
|
+
title: "Update task",
|
|
205
|
+
readOnlyHint: false,
|
|
206
|
+
},
|
|
207
|
+
}, async (input) => {
|
|
208
|
+
try {
|
|
209
|
+
const task = await taskStore.update(input.taskId, {
|
|
210
|
+
status: input.status,
|
|
211
|
+
subject: input.subject,
|
|
212
|
+
description: input.description,
|
|
213
|
+
activeForm: input.activeForm,
|
|
214
|
+
owner: input.owner,
|
|
215
|
+
addBlocks: input.addBlocks,
|
|
216
|
+
addBlockedBy: input.addBlockedBy,
|
|
217
|
+
metadata: input.metadata,
|
|
218
|
+
});
|
|
219
|
+
let message = `Updated task ${task.id}: "${task.subject}" (${task.status})`;
|
|
220
|
+
if (task.status === "completed") {
|
|
221
|
+
message +=
|
|
222
|
+
"\n\nTask completed. Call TaskList now to find your next available task or see if your work unblocked others.";
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: "text", text: message }],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
return {
|
|
230
|
+
isError: true,
|
|
231
|
+
content: [{ type: "text", text: `Failed to update task: ${err}` }],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// TaskList - List all tasks
|
|
236
|
+
server.registerTool("TaskList", {
|
|
237
|
+
title: "List Tasks",
|
|
238
|
+
description: `Use this tool to list all tasks in the task list.
|
|
239
|
+
|
|
240
|
+
## When to Use This Tool
|
|
241
|
+
|
|
242
|
+
- To see what tasks are available to work on (status: 'pending', no owner, not blocked)
|
|
243
|
+
- To check overall progress on the project
|
|
244
|
+
- To find tasks that are blocked and need dependencies resolved
|
|
245
|
+
- Before assigning tasks to teammates, to see what's available
|
|
246
|
+
- After completing a task, to check for newly unblocked work or claim the next available task
|
|
247
|
+
|
|
248
|
+
## Output
|
|
249
|
+
|
|
250
|
+
Returns a summary of each task:
|
|
251
|
+
- **id**: Task identifier (use with TaskGet, TaskUpdate)
|
|
252
|
+
- **subject**: Brief description of the task
|
|
253
|
+
- **status**: 'pending', 'in_progress', or 'completed'
|
|
254
|
+
- **owner**: Agent ID if assigned, empty if available
|
|
255
|
+
- **blockedBy**: List of open task IDs that must be resolved first
|
|
256
|
+
|
|
257
|
+
Use TaskGet with a specific task ID to view full details including description.`,
|
|
258
|
+
inputSchema: {},
|
|
259
|
+
annotations: {
|
|
260
|
+
title: "List tasks",
|
|
261
|
+
readOnlyHint: true,
|
|
262
|
+
},
|
|
263
|
+
}, async () => {
|
|
264
|
+
try {
|
|
265
|
+
const tasks = await taskStore.list();
|
|
266
|
+
const stats = await taskStore.getStats();
|
|
267
|
+
const summary = tasks.map((t) => ({
|
|
268
|
+
id: t.id,
|
|
269
|
+
subject: t.subject,
|
|
270
|
+
status: t.status,
|
|
271
|
+
owner: t.owner,
|
|
272
|
+
blockedBy: t.blockedBy.filter((id) => {
|
|
273
|
+
const blocker = tasks.find((task) => task.id === id);
|
|
274
|
+
return blocker && blocker.status !== "completed";
|
|
275
|
+
}),
|
|
276
|
+
}));
|
|
277
|
+
return {
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: JSON.stringify({
|
|
282
|
+
stats,
|
|
283
|
+
tasks: summary,
|
|
284
|
+
}, null, 2),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
return {
|
|
291
|
+
isError: true,
|
|
292
|
+
content: [{ type: "text", text: `Failed to list tasks: ${err}` }],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
package/package.json
CHANGED