@finityno/claude-code-acp 0.15.0 → 0.16.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.
@@ -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>;
@@ -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,39 @@ 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 if CLAUDE_CODE_TASK_LIST_ID is set
28
+ const taskListId = process.env.CLAUDE_CODE_TASK_LIST_ID;
29
+ if (taskListId) {
30
+ this.taskListId = taskListId;
31
+ this.taskStore = new TaskStore({
32
+ taskListId,
33
+ logger: this.logger,
34
+ onChange: (tasks) => {
35
+ // Broadcast task updates to all sessions
36
+ for (const sessionId of Object.keys(this.sessions)) {
37
+ this.client.sessionUpdate({
38
+ sessionId,
39
+ update: {
40
+ sessionUpdate: "tool_call_update",
41
+ toolCallId: "task-list-update",
42
+ _meta: {
43
+ claudeCode: {
44
+ taskListUpdate: {
45
+ taskListId,
46
+ tasks,
47
+ },
48
+ },
49
+ },
50
+ },
51
+ });
52
+ }
53
+ },
54
+ });
55
+ // Initialize and start watching for changes
56
+ this.taskStore.init().then(() => {
57
+ this.taskStore?.watch();
58
+ });
59
+ }
26
60
  }
27
61
  async initialize(request) {
28
62
  this.clientCapabilities = request.clientCapabilities;
@@ -583,7 +617,7 @@ export class ClaudeAcpAgent {
583
617
  }
584
618
  // Only add the acp MCP server if built-in tools are not disabled
585
619
  if (!params._meta?.disableBuiltInTools) {
586
- const server = createMcpServer(this, sessionId, this.clientCapabilities);
620
+ const server = createMcpServer(this, sessionId, this.clientCapabilities, this.taskStore);
587
621
  mcpServers["acp"] = {
588
622
  type: "sdk",
589
623
  name: "acp",
@@ -614,12 +648,21 @@ export class ClaudeAcpAgent {
614
648
  const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
615
649
  ? parseInt(process.env.MAX_THINKING_TOKENS, 10)
616
650
  : undefined;
651
+ // Build env with task list ID if configured
652
+ const sdkEnv = {
653
+ ...userProvidedOptions?.env,
654
+ };
655
+ if (this.taskListId) {
656
+ sdkEnv.CLAUDE_CODE_TASK_LIST_ID = this.taskListId;
657
+ }
617
658
  const options = {
618
659
  systemPrompt,
619
660
  settingSources: ["user", "project", "local"],
620
661
  stderr: (err) => this.logger.error(err),
621
662
  ...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
622
663
  ...userProvidedOptions,
664
+ // Pass task list ID to SDK so Claude's internal tools share our task list
665
+ ...(Object.keys(sdkEnv).length > 0 && { env: sdkEnv }),
623
666
  // Override certain fields that must be controlled by ACP
624
667
  cwd: params.cwd,
625
668
  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 task MCP tools registration
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";
@@ -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
  *
@@ -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,GACjD,SAAS,CAioBX;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"}
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"}
@@ -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 task management tools
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
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.15.0",
6
+ "version": "0.16.0",
7
7
  "description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",