agent-orchestration 0.5.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.
Files changed (63) hide show
  1. package/.cursor/rules/orchestrator-auto.mdc +107 -0
  2. package/.cursor/rules/orchestrator-main.mdc +86 -0
  3. package/.cursor/rules/orchestrator-sub.mdc +114 -0
  4. package/LICENSE +21 -0
  5. package/README.md +329 -0
  6. package/activeContext.md +37 -0
  7. package/dist/bin/cli.d.ts +11 -0
  8. package/dist/bin/cli.d.ts.map +1 -0
  9. package/dist/bin/cli.js +410 -0
  10. package/dist/bin/cli.js.map +1 -0
  11. package/dist/database.d.ts +91 -0
  12. package/dist/database.d.ts.map +1 -0
  13. package/dist/database.js +522 -0
  14. package/dist/database.js.map +1 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +56 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/models.d.ts +132 -0
  20. package/dist/models.d.ts.map +1 -0
  21. package/dist/models.js +124 -0
  22. package/dist/models.js.map +1 -0
  23. package/dist/tools/agent.d.ts +10 -0
  24. package/dist/tools/agent.d.ts.map +1 -0
  25. package/dist/tools/agent.js +185 -0
  26. package/dist/tools/agent.js.map +1 -0
  27. package/dist/tools/coordination.d.ts +6 -0
  28. package/dist/tools/coordination.d.ts.map +1 -0
  29. package/dist/tools/coordination.js +114 -0
  30. package/dist/tools/coordination.js.map +1 -0
  31. package/dist/tools/index.d.ts +9 -0
  32. package/dist/tools/index.d.ts.map +1 -0
  33. package/dist/tools/index.js +9 -0
  34. package/dist/tools/index.js.map +1 -0
  35. package/dist/tools/memory.d.ts +6 -0
  36. package/dist/tools/memory.d.ts.map +1 -0
  37. package/dist/tools/memory.js +108 -0
  38. package/dist/tools/memory.js.map +1 -0
  39. package/dist/tools/task.d.ts +6 -0
  40. package/dist/tools/task.d.ts.map +1 -0
  41. package/dist/tools/task.js +267 -0
  42. package/dist/tools/task.js.map +1 -0
  43. package/dist/tools/utility.d.ts +6 -0
  44. package/dist/tools/utility.d.ts.map +1 -0
  45. package/dist/tools/utility.js +162 -0
  46. package/dist/tools/utility.js.map +1 -0
  47. package/dist/utils/contextSync.d.ts +12 -0
  48. package/dist/utils/contextSync.d.ts.map +1 -0
  49. package/dist/utils/contextSync.js +124 -0
  50. package/dist/utils/contextSync.js.map +1 -0
  51. package/package.json +54 -0
  52. package/src/bin/cli.ts +430 -0
  53. package/src/database.ts +764 -0
  54. package/src/index.ts +71 -0
  55. package/src/models.ts +226 -0
  56. package/src/tools/agent.ts +241 -0
  57. package/src/tools/coordination.ts +152 -0
  58. package/src/tools/index.ts +9 -0
  59. package/src/tools/memory.ts +150 -0
  60. package/src/tools/task.ts +334 -0
  61. package/src/tools/utility.ts +202 -0
  62. package/src/utils/contextSync.ts +144 -0
  63. package/tsconfig.json +20 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Shared memory tools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { getDatabase } from '../database.js';
8
+ import { getCurrentAgentId } from './agent.js';
9
+
10
+ export function registerMemoryTools(server: McpServer): void {
11
+ // memory_set
12
+ server.tool(
13
+ 'memory_set',
14
+ 'Store a value in shared memory. Use namespaces to organize: context, decisions, findings, blockers.',
15
+ {
16
+ key: z.string().describe('The key to store the value under'),
17
+ value: z.string().describe('The value to store (will be stored as-is)'),
18
+ namespace: z
19
+ .string()
20
+ .optional()
21
+ .default('default')
22
+ .describe('Namespace for organization (context, decisions, findings, blockers)'),
23
+ ttl_seconds: z
24
+ .number()
25
+ .optional()
26
+ .describe('Time-to-live in seconds. Entry auto-deletes after this time.'),
27
+ },
28
+ async ({ key, value, namespace, ttl_seconds }) => {
29
+ const agentId = getCurrentAgentId();
30
+
31
+ const entry = getDatabase().setMemory({
32
+ key,
33
+ value,
34
+ namespace,
35
+ createdBy: agentId,
36
+ ttlSeconds: ttl_seconds ?? null,
37
+ });
38
+
39
+ const ttlInfo = entry.ttlSeconds ? ` (expires in ${entry.ttlSeconds}s)` : '';
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Stored '${key}' in namespace '${namespace}'${ttlInfo}`,
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ );
51
+
52
+ // memory_get
53
+ server.tool(
54
+ 'memory_get',
55
+ 'Retrieve a value from shared memory.',
56
+ {
57
+ key: z.string().describe('The key to retrieve'),
58
+ namespace: z.string().optional().default('default').describe('The namespace to search in'),
59
+ },
60
+ async ({ key, namespace }) => {
61
+ const entry = getDatabase().getMemory(key, namespace);
62
+
63
+ if (!entry) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: 'text',
68
+ text: `Key '${key}' not found in namespace '${namespace}'.`,
69
+ },
70
+ ],
71
+ };
72
+ }
73
+
74
+ const valueStr =
75
+ typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value, null, 2);
76
+
77
+ const lines = [
78
+ `**${namespace}:${key}**`,
79
+ '',
80
+ valueStr,
81
+ '',
82
+ `_Updated: ${entry.updatedAt.toISOString()}_`,
83
+ ];
84
+
85
+ if (entry.expiresAt) {
86
+ lines.push(`_Expires: ${entry.expiresAt.toISOString()}_`);
87
+ }
88
+
89
+ return {
90
+ content: [{ type: 'text', text: lines.join('\n') }],
91
+ };
92
+ }
93
+ );
94
+
95
+ // memory_list
96
+ server.tool(
97
+ 'memory_list',
98
+ 'List all keys in a namespace.',
99
+ {
100
+ namespace: z.string().optional().default('default').describe('The namespace to list'),
101
+ },
102
+ async ({ namespace }) => {
103
+ const entries = getDatabase().listMemory(namespace);
104
+
105
+ if (entries.length === 0) {
106
+ return {
107
+ content: [{ type: 'text', text: `No entries in namespace '${namespace}'.` }],
108
+ };
109
+ }
110
+
111
+ const lines = [`# Memory: ${namespace}\n`];
112
+
113
+ for (const entry of entries) {
114
+ const valuePreview =
115
+ typeof entry.value === 'string'
116
+ ? entry.value.slice(0, 100) + (entry.value.length > 100 ? '...' : '')
117
+ : JSON.stringify(entry.value).slice(0, 100);
118
+
119
+ lines.push(`- **${entry.key}**: ${valuePreview}`);
120
+ }
121
+
122
+ return {
123
+ content: [{ type: 'text', text: lines.join('\n') }],
124
+ };
125
+ }
126
+ );
127
+
128
+ // memory_delete
129
+ server.tool(
130
+ 'memory_delete',
131
+ 'Delete a value from shared memory.',
132
+ {
133
+ key: z.string().describe('The key to delete'),
134
+ namespace: z.string().optional().default('default').describe('The namespace'),
135
+ },
136
+ async ({ key, namespace }) => {
137
+ const deleted = getDatabase().deleteMemory(key, namespace);
138
+
139
+ if (deleted) {
140
+ return {
141
+ content: [{ type: 'text', text: `Deleted '${key}' from namespace '${namespace}'.` }],
142
+ };
143
+ }
144
+
145
+ return {
146
+ content: [{ type: 'text', text: `Key '${key}' not found in namespace '${namespace}'.` }],
147
+ };
148
+ }
149
+ );
150
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Task management tools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { getDatabase } from '../database.js';
8
+ import { TaskPriority, TaskStatus } from '../models.js';
9
+ import { getCurrentAgentId } from './agent.js';
10
+
11
+ export function registerTaskTools(server: McpServer): void {
12
+ // task_create
13
+ server.tool(
14
+ 'task_create',
15
+ 'Create a new task in the task queue.',
16
+ {
17
+ title: z.string().describe('Short title for the task'),
18
+ description: z.string().optional().default('').describe('Detailed description'),
19
+ priority: z
20
+ .enum(['low', 'normal', 'high', 'urgent'])
21
+ .optional()
22
+ .default('normal')
23
+ .describe('Priority level'),
24
+ assigned_to: z.string().optional().describe('Agent ID to assign to'),
25
+ dependencies: z
26
+ .array(z.string())
27
+ .optional()
28
+ .default([])
29
+ .describe('List of task IDs that must complete first'),
30
+ },
31
+ async ({ title, description, priority, assigned_to, dependencies }) => {
32
+ const agentId = getCurrentAgentId();
33
+
34
+ const task = getDatabase().createTask({
35
+ title,
36
+ description,
37
+ priority: priority as TaskPriority,
38
+ createdBy: agentId,
39
+ assignedTo: assigned_to ?? null,
40
+ dependencies,
41
+ });
42
+
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: `Task created: '${task.title}' (\`${task.id}\`)`,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ );
53
+
54
+ // task_claim
55
+ server.tool(
56
+ 'task_claim',
57
+ 'Claim a task to work on it. Sets status to in_progress.',
58
+ {
59
+ task_id: z
60
+ .string()
61
+ .optional()
62
+ .describe('Task ID to claim. If omitted, claims the next available task.'),
63
+ },
64
+ async ({ task_id }) => {
65
+ const agentId = getCurrentAgentId();
66
+ if (!agentId) {
67
+ return {
68
+ content: [{ type: 'text', text: 'Error: Not registered.' }],
69
+ };
70
+ }
71
+
72
+ const db = getDatabase();
73
+ let task;
74
+
75
+ if (task_id) {
76
+ task = db.getTask(task_id);
77
+ if (!task) {
78
+ return {
79
+ content: [{ type: 'text', text: `Task ${task_id} not found.` }],
80
+ };
81
+ }
82
+ } else {
83
+ task = db.getNextAvailableTask(agentId);
84
+ if (!task) {
85
+ return {
86
+ content: [{ type: 'text', text: 'No available tasks to claim.' }],
87
+ };
88
+ }
89
+ }
90
+
91
+ // Check dependencies
92
+ if (!db.checkDependenciesMet(task.id)) {
93
+ return {
94
+ content: [{ type: 'text', text: `Cannot claim: dependencies not met.` }],
95
+ };
96
+ }
97
+
98
+ // Claim it
99
+ const updated = db.updateTask(task.id, {
100
+ status: TaskStatus.IN_PROGRESS,
101
+ assignedTo: agentId,
102
+ });
103
+
104
+ if (updated) {
105
+ return {
106
+ content: [
107
+ {
108
+ type: 'text',
109
+ text: `Claimed task: '${updated.title}' (\`${updated.id}\`)`,
110
+ },
111
+ ],
112
+ };
113
+ }
114
+
115
+ return {
116
+ content: [{ type: 'text', text: 'Failed to claim task.' }],
117
+ };
118
+ }
119
+ );
120
+
121
+ // task_update
122
+ server.tool(
123
+ 'task_update',
124
+ 'Update a task status or progress.',
125
+ {
126
+ task_id: z.string().describe('The task ID to update'),
127
+ status: z
128
+ .enum(['pending', 'assigned', 'in_progress', 'completed', 'failed', 'cancelled'])
129
+ .optional()
130
+ .describe('New status'),
131
+ progress: z.number().min(0).max(100).optional().describe('Progress percentage (0-100)'),
132
+ output: z.string().optional().describe('Output or notes'),
133
+ },
134
+ async ({ task_id, status, progress, output }) => {
135
+ const db = getDatabase();
136
+ const task = db.getTask(task_id);
137
+
138
+ if (!task) {
139
+ return {
140
+ content: [{ type: 'text', text: `Task ${task_id} not found.` }],
141
+ };
142
+ }
143
+
144
+ const metadata = progress !== undefined ? { progress } : undefined;
145
+
146
+ const updated = db.updateTask(task_id, {
147
+ status: status as TaskStatus | undefined,
148
+ output,
149
+ metadata,
150
+ });
151
+
152
+ if (updated) {
153
+ const statusInfo = status ? ` → ${status}` : '';
154
+ const progressInfo = progress !== undefined ? ` (${progress}%)` : '';
155
+
156
+ return {
157
+ content: [
158
+ {
159
+ type: 'text',
160
+ text: `Updated task '${updated.title}'${statusInfo}${progressInfo}`,
161
+ },
162
+ ],
163
+ };
164
+ }
165
+
166
+ return {
167
+ content: [{ type: 'text', text: 'Failed to update task.' }],
168
+ };
169
+ }
170
+ );
171
+
172
+ // task_complete
173
+ server.tool(
174
+ 'task_complete',
175
+ 'Mark a task as completed with optional output.',
176
+ {
177
+ task_id: z.string().describe('The task ID to complete'),
178
+ output: z.string().optional().describe('Summary of what was done'),
179
+ },
180
+ async ({ task_id, output }) => {
181
+ const db = getDatabase();
182
+ const task = db.getTask(task_id);
183
+
184
+ if (!task) {
185
+ return {
186
+ content: [{ type: 'text', text: `Task ${task_id} not found.` }],
187
+ };
188
+ }
189
+
190
+ const updated = db.updateTask(task_id, {
191
+ status: TaskStatus.COMPLETED,
192
+ output,
193
+ });
194
+
195
+ if (updated) {
196
+ return {
197
+ content: [
198
+ {
199
+ type: 'text',
200
+ text: `✅ Task completed: '${updated.title}'`,
201
+ },
202
+ ],
203
+ };
204
+ }
205
+
206
+ return {
207
+ content: [{ type: 'text', text: 'Failed to complete task.' }],
208
+ };
209
+ }
210
+ );
211
+
212
+ // task_list
213
+ server.tool(
214
+ 'task_list',
215
+ 'List tasks with optional filters.',
216
+ {
217
+ status: z
218
+ .enum(['pending', 'assigned', 'in_progress', 'completed', 'failed', 'cancelled'])
219
+ .optional()
220
+ .describe('Filter by status'),
221
+ assigned_to: z.string().optional().describe('Filter by assigned agent ID'),
222
+ mine: z.boolean().optional().describe('Show only my tasks'),
223
+ },
224
+ async ({ status, assigned_to, mine }) => {
225
+ const agentId = getCurrentAgentId();
226
+ const db = getDatabase();
227
+
228
+ let assignee = assigned_to;
229
+ if (mine && agentId) {
230
+ assignee = agentId;
231
+ }
232
+
233
+ const tasks = db.listTasks({
234
+ status: status as TaskStatus | undefined,
235
+ assignedTo: assignee,
236
+ });
237
+
238
+ if (tasks.length === 0) {
239
+ return {
240
+ content: [{ type: 'text', text: 'No tasks found.' }],
241
+ };
242
+ }
243
+
244
+ const lines = ['# Tasks\n'];
245
+
246
+ for (const task of tasks) {
247
+ const emoji = {
248
+ pending: '⏳',
249
+ assigned: '📋',
250
+ in_progress: '🔄',
251
+ completed: '✅',
252
+ failed: '❌',
253
+ cancelled: '🚫',
254
+ }[task.status];
255
+
256
+ lines.push(`${emoji} **${task.title}** (\`${task.id.slice(0, 8)}...\`)`);
257
+ lines.push(` Status: ${task.status} | Assigned: ${task.assignedTo || 'none'}`);
258
+ }
259
+
260
+ return {
261
+ content: [{ type: 'text', text: lines.join('\n') }],
262
+ };
263
+ }
264
+ );
265
+
266
+ // is_my_turn
267
+ server.tool(
268
+ 'is_my_turn',
269
+ "Check if it's your turn to work on a task or if work is available.",
270
+ {
271
+ task_id: z
272
+ .string()
273
+ .optional()
274
+ .describe('Specific task ID, or leave empty to check for any available task'),
275
+ },
276
+ async ({ task_id }) => {
277
+ const agentId = getCurrentAgentId();
278
+ if (!agentId) {
279
+ return {
280
+ content: [{ type: 'text', text: 'Error: Not registered.' }],
281
+ };
282
+ }
283
+
284
+ const db = getDatabase();
285
+
286
+ if (task_id) {
287
+ const task = db.getTask(task_id);
288
+ if (!task) {
289
+ return {
290
+ content: [{ type: 'text', text: `Task ${task_id} not found.` }],
291
+ };
292
+ }
293
+
294
+ if (task.assignedTo !== agentId) {
295
+ return {
296
+ content: [
297
+ {
298
+ type: 'text',
299
+ text: `No - assigned to ${task.assignedTo || 'no one'}.`,
300
+ },
301
+ ],
302
+ };
303
+ }
304
+
305
+ if (!db.checkDependenciesMet(task_id)) {
306
+ return {
307
+ content: [{ type: 'text', text: 'No - waiting for dependencies.' }],
308
+ };
309
+ }
310
+
311
+ if (task.status === TaskStatus.COMPLETED) {
312
+ return {
313
+ content: [{ type: 'text', text: 'No - already completed.' }],
314
+ };
315
+ }
316
+
317
+ return {
318
+ content: [{ type: 'text', text: 'Yes - task is ready for you. Use task_claim to start.' }],
319
+ };
320
+ } else {
321
+ const available = db.getNextAvailableTask(agentId);
322
+ if (available) {
323
+ return {
324
+ content: [{ type: 'text', text: `Yes - '${available.title}' is available.` }],
325
+ };
326
+ }
327
+
328
+ return {
329
+ content: [{ type: 'text', text: 'No - no tasks available.' }],
330
+ };
331
+ }
332
+ }
333
+ );
334
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Utility tools (bootstrap, claim_todo)
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { getDatabase } from '../database.js';
8
+ import { AgentRole, AgentStatus, TaskPriority, TaskStatus } from '../models.js';
9
+ import {
10
+ getCurrentAgentId,
11
+ setCurrentAgent,
12
+ } from './agent.js';
13
+ import { syncToActiveContext } from '../utils/contextSync.js';
14
+
15
+ export function registerUtilityTools(server: McpServer): void {
16
+ // bootstrap
17
+ server.tool(
18
+ 'bootstrap',
19
+ 'Initialize agent session: register (if needed), get current focus, pending tasks, and recent decisions. Call this once at the start of your session.',
20
+ {
21
+ name: z
22
+ .string()
23
+ .optional()
24
+ .describe('Agent name. If not provided, uses env MCP_ORCH_AGENT_NAME or generates one.'),
25
+ role: z
26
+ .enum(['main', 'sub'])
27
+ .optional()
28
+ .default('sub')
29
+ .describe("Agent role. Defaults to env MCP_ORCH_AGENT_ROLE or 'sub'."),
30
+ },
31
+ async ({ name, role }) => {
32
+ const db = getDatabase();
33
+
34
+ // Get or generate agent name
35
+ let agentName = name ?? process.env.MCP_ORCH_AGENT_NAME;
36
+ if (!agentName) {
37
+ agentName = `agent-${Date.now()}`;
38
+ }
39
+
40
+ const agentRole =
41
+ role === 'main'
42
+ ? AgentRole.MAIN
43
+ : role === 'sub'
44
+ ? AgentRole.SUB
45
+ : process.env.MCP_ORCH_AGENT_ROLE === 'main'
46
+ ? AgentRole.MAIN
47
+ : AgentRole.SUB;
48
+
49
+ const capabilities = (process.env.MCP_ORCH_CAPABILITIES ?? 'code').split(',');
50
+
51
+ // Check if agent already exists
52
+ let agent = db.getAgentByName(agentName);
53
+
54
+ if (agent) {
55
+ // Reconnect
56
+ setCurrentAgent(agent.id, agent.name);
57
+ db.updateAgentHeartbeat(agent.id, AgentStatus.ACTIVE);
58
+ } else {
59
+ // Register new
60
+ agent = db.createAgent({
61
+ name: agentName,
62
+ role: agentRole,
63
+ capabilities,
64
+ status: AgentStatus.ACTIVE,
65
+ });
66
+ setCurrentAgent(agent.id, agent.name);
67
+ }
68
+
69
+ // Get current context
70
+ const focusEntry = db.getMemory('current_focus', 'context');
71
+ const focusText = focusEntry ? String(focusEntry.value) : 'Not set';
72
+
73
+ // Get pending tasks for this agent
74
+ const myTasks = db.listTasks({ assignedTo: agent.id });
75
+ const pendingTasks = myTasks.filter((t) =>
76
+ ['pending', 'assigned'].includes(t.status)
77
+ );
78
+
79
+ // Get recent decisions
80
+ const decisions = db.listMemory('decisions');
81
+
82
+ // Sync context
83
+ syncToActiveContext();
84
+
85
+ const lines: string[] = [
86
+ '# Session Initialized',
87
+ '',
88
+ `**Agent**: ${agent.name} (\`${agent.id}\`)`,
89
+ `**Role**: ${agent.role}`,
90
+ '',
91
+ '## Current Focus',
92
+ focusText,
93
+ '',
94
+ '## Your Pending Tasks',
95
+ ];
96
+
97
+ if (pendingTasks.length > 0) {
98
+ for (const t of pendingTasks.slice(0, 5)) {
99
+ lines.push(`- ${t.title} (\`${t.id.slice(0, 8)}...\`)`);
100
+ }
101
+ } else {
102
+ lines.push('_No tasks assigned to you._');
103
+ }
104
+
105
+ lines.push('', '## Recent Decisions');
106
+
107
+ if (decisions.length > 0) {
108
+ for (const d of decisions.slice(0, 5)) {
109
+ lines.push(`- **${d.key}**: ${String(d.value).slice(0, 80)}`);
110
+ }
111
+ } else {
112
+ lines.push('_No decisions recorded._');
113
+ }
114
+
115
+ lines.push('', '---', 'Use `is_my_turn` to check for available work.');
116
+
117
+ return {
118
+ content: [{ type: 'text', text: lines.join('\n') }],
119
+ };
120
+ }
121
+ );
122
+
123
+ // claim_todo
124
+ server.tool(
125
+ 'claim_todo',
126
+ 'FOR SUB-AGENTS: Register yourself AND claim a specific task in one call. Use this when you were spawned to work on a specific todo. This creates the task if it doesn\'t exist, then claims it for you.',
127
+ {
128
+ title: z.string().describe('The title of the todo/task you were spawned to work on'),
129
+ description: z.string().optional().default('').describe('Additional details about the task'),
130
+ priority: z
131
+ .enum(['low', 'normal', 'high', 'urgent'])
132
+ .optional()
133
+ .default('normal')
134
+ .describe('Priority level'),
135
+ },
136
+ async ({ title, description, priority }) => {
137
+ const db = getDatabase();
138
+
139
+ // Generate agent name
140
+ const agentName = `sub-${Date.now()}`;
141
+
142
+ // Register as sub-agent
143
+ const agent = db.createAgent({
144
+ name: agentName,
145
+ role: AgentRole.SUB,
146
+ capabilities: ['code'],
147
+ status: AgentStatus.BUSY, // Already working
148
+ });
149
+ setCurrentAgent(agent.id, agent.name);
150
+
151
+ // Check if a task with this title already exists and is pending
152
+ const allTasks = db.listTasks();
153
+ let task = allTasks.find(
154
+ (t) =>
155
+ t.title.toLowerCase().trim() === title.toLowerCase().trim() &&
156
+ ['pending', 'assigned'].includes(t.status)
157
+ );
158
+
159
+ if (task) {
160
+ // Claim the existing task
161
+ task = db.updateTask(task.id, {
162
+ assignedTo: agent.id,
163
+ status: TaskStatus.IN_PROGRESS,
164
+ })!;
165
+ } else {
166
+ // Create a new task and claim it
167
+ task = db.createTask({
168
+ title,
169
+ description,
170
+ priority: priority as TaskPriority,
171
+ status: TaskStatus.IN_PROGRESS,
172
+ assignedTo: agent.id,
173
+ createdBy: agent.id,
174
+ startedAt: new Date(),
175
+ });
176
+ }
177
+
178
+ // Sync context
179
+ syncToActiveContext();
180
+
181
+ const lines: string[] = [
182
+ '# Task Claimed',
183
+ '',
184
+ `**You are**: ${agent.name} (\`${agent.id}\`)`,
185
+ `**Working on**: ${task.title}`,
186
+ `**Task ID**: \`${task.id}\``,
187
+ '',
188
+ '---',
189
+ '',
190
+ 'Now you can start working. Remember to:',
191
+ '1. `lock_acquire` on any files you edit',
192
+ '2. `task_update` to report progress',
193
+ '3. `task_complete` when done',
194
+ '4. `agent_unregister` when finished',
195
+ ];
196
+
197
+ return {
198
+ content: [{ type: 'text', text: lines.join('\n') }],
199
+ };
200
+ }
201
+ );
202
+ }