@compilr-dev/agents 0.3.7 → 0.3.9
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/providers/fireworks.d.ts +8 -0
- package/dist/providers/fireworks.js +15 -0
- package/dist/providers/openai-compatible.d.ts +9 -0
- package/dist/providers/openai-compatible.js +14 -3
- package/dist/tools/builtin/index.d.ts +1 -1
- package/dist/tools/builtin/todo.d.ts +58 -0
- package/dist/tools/builtin/todo.js +158 -7
- package/package.json +1 -1
|
@@ -57,6 +57,14 @@ export declare class FireworksProvider extends OpenAICompatibleProvider {
|
|
|
57
57
|
* Fireworks AI uses standard OpenAI body format
|
|
58
58
|
*/
|
|
59
59
|
protected buildProviderSpecificBody(_options?: ChatOptions): Record<string, unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Extract cache statistics from Fireworks response headers.
|
|
62
|
+
* Fireworks returns cache stats in headers rather than the JSON body.
|
|
63
|
+
* @see https://docs.fireworks.ai/guides/prompt-caching
|
|
64
|
+
*/
|
|
65
|
+
protected extractCacheStatsFromHeaders(headers: Headers): {
|
|
66
|
+
cacheReadTokens?: number;
|
|
67
|
+
};
|
|
60
68
|
/**
|
|
61
69
|
* Map HTTP errors with Fireworks AI-specific messages
|
|
62
70
|
*/
|
|
@@ -65,6 +65,21 @@ export class FireworksProvider extends OpenAICompatibleProvider {
|
|
|
65
65
|
buildProviderSpecificBody(_options) {
|
|
66
66
|
return {};
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract cache statistics from Fireworks response headers.
|
|
70
|
+
* Fireworks returns cache stats in headers rather than the JSON body.
|
|
71
|
+
* @see https://docs.fireworks.ai/guides/prompt-caching
|
|
72
|
+
*/
|
|
73
|
+
extractCacheStatsFromHeaders(headers) {
|
|
74
|
+
const cachedTokens = headers.get('fireworks-cached-prompt-tokens');
|
|
75
|
+
if (cachedTokens) {
|
|
76
|
+
const parsed = parseInt(cachedTokens, 10);
|
|
77
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
78
|
+
return { cacheReadTokens: parsed };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
68
83
|
/**
|
|
69
84
|
* Map HTTP errors with Fireworks AI-specific messages
|
|
70
85
|
*/
|
|
@@ -149,6 +149,15 @@ export declare abstract class OpenAICompatibleProvider implements LLMProvider {
|
|
|
149
149
|
* @returns ProviderError with appropriate message
|
|
150
150
|
*/
|
|
151
151
|
protected abstract mapConnectionError(error: Error): ProviderError;
|
|
152
|
+
/**
|
|
153
|
+
* Extract cache statistics from response headers.
|
|
154
|
+
* Override in subclasses for providers that return cache stats in headers (e.g., Fireworks).
|
|
155
|
+
* @param headers Response headers
|
|
156
|
+
* @returns Partial LLMUsage with cache stats
|
|
157
|
+
*/
|
|
158
|
+
protected extractCacheStatsFromHeaders(_headers: Headers): {
|
|
159
|
+
cacheReadTokens?: number;
|
|
160
|
+
};
|
|
152
161
|
/**
|
|
153
162
|
* Stream chat completion from the provider
|
|
154
163
|
*
|
|
@@ -42,6 +42,15 @@ export class OpenAICompatibleProvider {
|
|
|
42
42
|
this.defaultMaxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
43
43
|
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Extract cache statistics from response headers.
|
|
47
|
+
* Override in subclasses for providers that return cache stats in headers (e.g., Fireworks).
|
|
48
|
+
* @param headers Response headers
|
|
49
|
+
* @returns Partial LLMUsage with cache stats
|
|
50
|
+
*/
|
|
51
|
+
extractCacheStatsFromHeaders(_headers) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
45
54
|
// ==================== SHARED IMPLEMENTATION ====================
|
|
46
55
|
/**
|
|
47
56
|
* Stream chat completion from the provider
|
|
@@ -106,6 +115,8 @@ export class OpenAICompatibleProvider {
|
|
|
106
115
|
const errorBody = await response.text();
|
|
107
116
|
throw this.mapHttpError(response.status, errorBody, model);
|
|
108
117
|
}
|
|
118
|
+
// Extract cache stats from headers (for providers like Fireworks)
|
|
119
|
+
const headerCacheStats = this.extractCacheStatsFromHeaders(response.headers);
|
|
109
120
|
const reader = response.body?.getReader();
|
|
110
121
|
if (!reader) {
|
|
111
122
|
throw new ProviderError('No response body', this.name);
|
|
@@ -153,12 +164,12 @@ export class OpenAICompatibleProvider {
|
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
166
|
}
|
|
156
|
-
// Yield done chunk with usage
|
|
167
|
+
// Yield done chunk with usage (merge header-based cache stats)
|
|
157
168
|
yield {
|
|
158
169
|
type: 'done',
|
|
159
170
|
usage: usage
|
|
160
|
-
? { ...usage, debugPayload }
|
|
161
|
-
: { inputTokens: 0, outputTokens: 0, debugPayload },
|
|
171
|
+
? { ...usage, ...headerCacheStats, debugPayload }
|
|
172
|
+
: { inputTokens: 0, outputTokens: 0, ...headerCacheStats, debugPayload },
|
|
162
173
|
};
|
|
163
174
|
}
|
|
164
175
|
catch (error) {
|
|
@@ -22,7 +22,7 @@ export type { GlobInput } from './glob.js';
|
|
|
22
22
|
export { editTool, createEditTool } from './edit.js';
|
|
23
23
|
export type { EditInput } from './edit.js';
|
|
24
24
|
export { todoWriteTool, todoReadTool, createTodoTools, TodoStore, resetDefaultTodoStore, getDefaultTodoStore, createIsolatedTodoStore, cleanupTodoContextMessages, getTodoContextStats, } from './todo.js';
|
|
25
|
-
export type { TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, } from './todo.js';
|
|
25
|
+
export type { TodoWriteInput, TodoReadInput, TodoClaimInput, TodoHandoffInput, TodoItem, TodoStatus, TodoContextCleanupOptions, } from './todo.js';
|
|
26
26
|
export { createTaskTool, defaultAgentTypes } from './task.js';
|
|
27
27
|
export type { TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, } from './task.js';
|
|
28
28
|
export { webFetchTool, createWebFetchTool } from './web-fetch.js';
|
|
@@ -33,6 +33,11 @@ export interface TodoItem {
|
|
|
33
33
|
* Optional priority (higher = more important)
|
|
34
34
|
*/
|
|
35
35
|
priority?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Owner agent ID (e.g., 'pm', 'arch', 'dev')
|
|
38
|
+
* If undefined/null, the task is unassigned
|
|
39
|
+
*/
|
|
40
|
+
owner?: string;
|
|
36
41
|
/**
|
|
37
42
|
* Creation timestamp
|
|
38
43
|
*/
|
|
@@ -54,6 +59,7 @@ export interface TodoWriteInput {
|
|
|
54
59
|
status: TodoStatus;
|
|
55
60
|
activeForm?: string;
|
|
56
61
|
priority?: number;
|
|
62
|
+
owner?: string;
|
|
57
63
|
}>;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
@@ -68,6 +74,13 @@ export interface TodoReadInput {
|
|
|
68
74
|
* Include completed tasks (default: true)
|
|
69
75
|
*/
|
|
70
76
|
includeCompleted?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Filter by owner (optional)
|
|
79
|
+
* - Specific agent ID: Return only tasks owned by that agent
|
|
80
|
+
* - 'unassigned': Return only tasks without an owner
|
|
81
|
+
* - undefined: Return all tasks (no filter)
|
|
82
|
+
*/
|
|
83
|
+
owner?: string;
|
|
71
84
|
}
|
|
72
85
|
/**
|
|
73
86
|
* TodoStore manages the in-memory task list
|
|
@@ -83,6 +96,11 @@ export declare class TodoStore {
|
|
|
83
96
|
* Get todos filtered by status
|
|
84
97
|
*/
|
|
85
98
|
getByStatus(status: TodoStatus): TodoItem[];
|
|
99
|
+
/**
|
|
100
|
+
* Get todos filtered by owner
|
|
101
|
+
* @param owner - Agent ID, or 'unassigned' for tasks without owner
|
|
102
|
+
*/
|
|
103
|
+
getByOwner(owner: string): TodoItem[];
|
|
86
104
|
/**
|
|
87
105
|
* Replace all todos with a new list
|
|
88
106
|
*/
|
|
@@ -91,6 +109,7 @@ export declare class TodoStore {
|
|
|
91
109
|
status: TodoStatus;
|
|
92
110
|
activeForm?: string;
|
|
93
111
|
priority?: number;
|
|
112
|
+
owner?: string;
|
|
94
113
|
}>): void;
|
|
95
114
|
/**
|
|
96
115
|
* Add a single todo
|
|
@@ -100,6 +119,7 @@ export declare class TodoStore {
|
|
|
100
119
|
status: TodoStatus;
|
|
101
120
|
activeForm?: string;
|
|
102
121
|
priority?: number;
|
|
122
|
+
owner?: string;
|
|
103
123
|
}): TodoItem;
|
|
104
124
|
/**
|
|
105
125
|
* Update a todo by ID
|
|
@@ -117,6 +137,12 @@ export declare class TodoStore {
|
|
|
117
137
|
* Get count by status
|
|
118
138
|
*/
|
|
119
139
|
getCounts(): Record<TodoStatus, number>;
|
|
140
|
+
/**
|
|
141
|
+
* Get counts by owner
|
|
142
|
+
* Returns a map of owner ID to count
|
|
143
|
+
* 'unassigned' key contains count of todos without owner
|
|
144
|
+
*/
|
|
145
|
+
getCountsByOwner(): Record<string, number>;
|
|
120
146
|
}
|
|
121
147
|
/**
|
|
122
148
|
* TodoWrite tool - Update the task list
|
|
@@ -126,12 +152,44 @@ export declare const todoWriteTool: Tool<TodoWriteInput>;
|
|
|
126
152
|
* TodoRead tool - Get the current task list
|
|
127
153
|
*/
|
|
128
154
|
export declare const todoReadTool: Tool<TodoReadInput>;
|
|
155
|
+
/**
|
|
156
|
+
* Input for TodoClaim tool
|
|
157
|
+
*/
|
|
158
|
+
export interface TodoClaimInput {
|
|
159
|
+
/**
|
|
160
|
+
* The todo ID to claim
|
|
161
|
+
*/
|
|
162
|
+
todoId: string;
|
|
163
|
+
/**
|
|
164
|
+
* The agent ID claiming the todo
|
|
165
|
+
*/
|
|
166
|
+
agentId: string;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Input for TodoHandoff tool
|
|
170
|
+
*/
|
|
171
|
+
export interface TodoHandoffInput {
|
|
172
|
+
/**
|
|
173
|
+
* The todo ID to hand off
|
|
174
|
+
*/
|
|
175
|
+
todoId: string;
|
|
176
|
+
/**
|
|
177
|
+
* The agent ID to hand off to
|
|
178
|
+
*/
|
|
179
|
+
toAgentId: string;
|
|
180
|
+
/**
|
|
181
|
+
* Optional notes about the handoff
|
|
182
|
+
*/
|
|
183
|
+
notes?: string;
|
|
184
|
+
}
|
|
129
185
|
/**
|
|
130
186
|
* Factory to create todo tools with a custom store
|
|
131
187
|
*/
|
|
132
188
|
export declare function createTodoTools(store?: TodoStore): {
|
|
133
189
|
todoWrite: Tool<TodoWriteInput>;
|
|
134
190
|
todoRead: Tool<TodoReadInput>;
|
|
191
|
+
todoClaim: Tool<TodoClaimInput>;
|
|
192
|
+
todoHandoff: Tool<TodoHandoffInput>;
|
|
135
193
|
store: TodoStore;
|
|
136
194
|
};
|
|
137
195
|
/**
|
|
@@ -39,6 +39,16 @@ export class TodoStore {
|
|
|
39
39
|
getByStatus(status) {
|
|
40
40
|
return this.getAll().filter((t) => t.status === status);
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Get todos filtered by owner
|
|
44
|
+
* @param owner - Agent ID, or 'unassigned' for tasks without owner
|
|
45
|
+
*/
|
|
46
|
+
getByOwner(owner) {
|
|
47
|
+
if (owner === 'unassigned') {
|
|
48
|
+
return this.getAll().filter((t) => !t.owner);
|
|
49
|
+
}
|
|
50
|
+
return this.getAll().filter((t) => t.owner === owner);
|
|
51
|
+
}
|
|
42
52
|
/**
|
|
43
53
|
* Replace all todos with a new list
|
|
44
54
|
*/
|
|
@@ -53,6 +63,7 @@ export class TodoStore {
|
|
|
53
63
|
status: todo.status,
|
|
54
64
|
activeForm: todo.activeForm,
|
|
55
65
|
priority: todo.priority,
|
|
66
|
+
owner: todo.owner,
|
|
56
67
|
createdAt: now,
|
|
57
68
|
updatedAt: now,
|
|
58
69
|
});
|
|
@@ -70,6 +81,7 @@ export class TodoStore {
|
|
|
70
81
|
status: todo.status,
|
|
71
82
|
activeForm: todo.activeForm,
|
|
72
83
|
priority: todo.priority,
|
|
84
|
+
owner: todo.owner,
|
|
73
85
|
createdAt: now,
|
|
74
86
|
updatedAt: now,
|
|
75
87
|
};
|
|
@@ -117,6 +129,23 @@ export class TodoStore {
|
|
|
117
129
|
}
|
|
118
130
|
return counts;
|
|
119
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Get counts by owner
|
|
134
|
+
* Returns a map of owner ID to count
|
|
135
|
+
* 'unassigned' key contains count of todos without owner
|
|
136
|
+
*/
|
|
137
|
+
getCountsByOwner() {
|
|
138
|
+
const counts = { unassigned: 0 };
|
|
139
|
+
for (const todo of this.todos.values()) {
|
|
140
|
+
if (todo.owner) {
|
|
141
|
+
counts[todo.owner] = (counts[todo.owner] ?? 0) + 1;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
counts['unassigned']++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return counts;
|
|
148
|
+
}
|
|
120
149
|
}
|
|
121
150
|
/**
|
|
122
151
|
* Global default store (can be overridden with factory functions)
|
|
@@ -127,8 +156,9 @@ const defaultStore = new TodoStore();
|
|
|
127
156
|
*/
|
|
128
157
|
export const todoWriteTool = defineTool({
|
|
129
158
|
name: 'todo_write',
|
|
130
|
-
description: 'Update the
|
|
131
|
-
'
|
|
159
|
+
description: 'Update the SESSION todo list (shown in CLI footer). These are ephemeral tasks for the current session only - ' +
|
|
160
|
+
'NOT persistent backlog items. For persistent project backlog, use workitem_* tools. ' +
|
|
161
|
+
'Provide the complete list to replace current todos.',
|
|
132
162
|
inputSchema: {
|
|
133
163
|
type: 'object',
|
|
134
164
|
properties: {
|
|
@@ -155,6 +185,10 @@ export const todoWriteTool = defineTool({
|
|
|
155
185
|
type: 'number',
|
|
156
186
|
description: 'Priority (higher = more important)',
|
|
157
187
|
},
|
|
188
|
+
owner: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
description: 'Owner agent ID (e.g., "dev", "pm", "arch"). Omit for unassigned tasks.',
|
|
191
|
+
},
|
|
158
192
|
},
|
|
159
193
|
required: ['content', 'status'],
|
|
160
194
|
},
|
|
@@ -186,7 +220,8 @@ export const todoWriteTool = defineTool({
|
|
|
186
220
|
*/
|
|
187
221
|
export const todoReadTool = defineTool({
|
|
188
222
|
name: 'todo_read',
|
|
189
|
-
description: 'Get the
|
|
223
|
+
description: 'Get the SESSION todo list (shown in CLI footer). These are ephemeral session tasks - ' +
|
|
224
|
+
'for persistent project backlog, use workitem_query. Optionally filter by status or owner.',
|
|
190
225
|
inputSchema: {
|
|
191
226
|
type: 'object',
|
|
192
227
|
properties: {
|
|
@@ -199,6 +234,10 @@ export const todoReadTool = defineTool({
|
|
|
199
234
|
type: 'boolean',
|
|
200
235
|
description: 'Include completed tasks (default: true)',
|
|
201
236
|
},
|
|
237
|
+
owner: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'Filter by owner agent ID, or "unassigned" for tasks without owner (optional)',
|
|
240
|
+
},
|
|
202
241
|
},
|
|
203
242
|
},
|
|
204
243
|
execute: (input) => {
|
|
@@ -211,6 +250,15 @@ export const todoReadTool = defineTool({
|
|
|
211
250
|
if (input.includeCompleted === false) {
|
|
212
251
|
todos = todos.filter((t) => t.status !== 'completed');
|
|
213
252
|
}
|
|
253
|
+
// Filter by owner if specified
|
|
254
|
+
if (input.owner) {
|
|
255
|
+
if (input.owner === 'unassigned') {
|
|
256
|
+
todos = todos.filter((t) => !t.owner);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
todos = todos.filter((t) => t.owner === input.owner);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
214
262
|
const counts = defaultStore.getCounts();
|
|
215
263
|
return Promise.resolve(createSuccessResult({
|
|
216
264
|
todos: todos.map((t) => ({
|
|
@@ -219,6 +267,7 @@ export const todoReadTool = defineTool({
|
|
|
219
267
|
status: t.status,
|
|
220
268
|
activeForm: t.activeForm,
|
|
221
269
|
priority: t.priority,
|
|
270
|
+
owner: t.owner,
|
|
222
271
|
})),
|
|
223
272
|
counts,
|
|
224
273
|
total: todos.length,
|
|
@@ -232,8 +281,8 @@ export function createTodoTools(store) {
|
|
|
232
281
|
const todoStore = store ?? new TodoStore();
|
|
233
282
|
const todoWrite = defineTool({
|
|
234
283
|
name: 'todo_write',
|
|
235
|
-
description: 'Update the
|
|
236
|
-
'
|
|
284
|
+
description: 'Update the SESSION todo list (shown in CLI footer). These are ephemeral tasks for the current session only - ' +
|
|
285
|
+
'NOT persistent backlog items. For persistent project backlog, use workitem_* tools.',
|
|
237
286
|
inputSchema: {
|
|
238
287
|
type: 'object',
|
|
239
288
|
properties: {
|
|
@@ -247,6 +296,7 @@ export function createTodoTools(store) {
|
|
|
247
296
|
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
248
297
|
activeForm: { type: 'string' },
|
|
249
298
|
priority: { type: 'number' },
|
|
299
|
+
owner: { type: 'string' },
|
|
250
300
|
},
|
|
251
301
|
required: ['content', 'status'],
|
|
252
302
|
},
|
|
@@ -275,7 +325,8 @@ export function createTodoTools(store) {
|
|
|
275
325
|
});
|
|
276
326
|
const todoRead = defineTool({
|
|
277
327
|
name: 'todo_read',
|
|
278
|
-
description: 'Get the
|
|
328
|
+
description: 'Get the SESSION todo list (shown in CLI footer). These are ephemeral session tasks - ' +
|
|
329
|
+
'for persistent project backlog, use workitem_query.',
|
|
279
330
|
inputSchema: {
|
|
280
331
|
type: 'object',
|
|
281
332
|
properties: {
|
|
@@ -286,6 +337,10 @@ export function createTodoTools(store) {
|
|
|
286
337
|
includeCompleted: {
|
|
287
338
|
type: 'boolean',
|
|
288
339
|
},
|
|
340
|
+
owner: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
description: 'Filter by owner agent ID, or "unassigned" for tasks without owner',
|
|
343
|
+
},
|
|
289
344
|
},
|
|
290
345
|
},
|
|
291
346
|
execute: (input) => {
|
|
@@ -296,6 +351,14 @@ export function createTodoTools(store) {
|
|
|
296
351
|
if (input.includeCompleted === false) {
|
|
297
352
|
todos = todos.filter((t) => t.status !== 'completed');
|
|
298
353
|
}
|
|
354
|
+
if (input.owner) {
|
|
355
|
+
if (input.owner === 'unassigned') {
|
|
356
|
+
todos = todos.filter((t) => !t.owner);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
todos = todos.filter((t) => t.owner === input.owner);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
299
362
|
const counts = todoStore.getCounts();
|
|
300
363
|
return Promise.resolve(createSuccessResult({
|
|
301
364
|
todos: todos.map((t) => ({
|
|
@@ -304,6 +367,7 @@ export function createTodoTools(store) {
|
|
|
304
367
|
status: t.status,
|
|
305
368
|
activeForm: t.activeForm,
|
|
306
369
|
priority: t.priority,
|
|
370
|
+
owner: t.owner,
|
|
307
371
|
})),
|
|
308
372
|
counts,
|
|
309
373
|
total: todos.length,
|
|
@@ -311,7 +375,94 @@ export function createTodoTools(store) {
|
|
|
311
375
|
},
|
|
312
376
|
silent: true,
|
|
313
377
|
});
|
|
314
|
-
|
|
378
|
+
const todoClaim = defineTool({
|
|
379
|
+
name: 'todo_claim',
|
|
380
|
+
description: 'Claim an unassigned SESSION todo (from CLI footer). For persistent backlog items, use workitem_claim.',
|
|
381
|
+
inputSchema: {
|
|
382
|
+
type: 'object',
|
|
383
|
+
properties: {
|
|
384
|
+
todoId: {
|
|
385
|
+
type: 'string',
|
|
386
|
+
description: 'The todo ID to claim',
|
|
387
|
+
},
|
|
388
|
+
agentId: {
|
|
389
|
+
type: 'string',
|
|
390
|
+
description: 'The agent ID claiming the todo',
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
required: ['todoId', 'agentId'],
|
|
394
|
+
},
|
|
395
|
+
execute: (input) => {
|
|
396
|
+
const todos = todoStore.getAll();
|
|
397
|
+
const todo = todos.find((t) => t.id === input.todoId);
|
|
398
|
+
if (!todo) {
|
|
399
|
+
return Promise.resolve(createErrorResult(`Todo "${input.todoId}" not found`));
|
|
400
|
+
}
|
|
401
|
+
if (todo.owner) {
|
|
402
|
+
return Promise.resolve(createErrorResult(`Todo "${input.todoId}" is already owned by "${todo.owner}"`));
|
|
403
|
+
}
|
|
404
|
+
const updated = todoStore.update(input.todoId, { owner: input.agentId });
|
|
405
|
+
if (!updated) {
|
|
406
|
+
return Promise.resolve(createErrorResult(`Failed to update todo "${input.todoId}"`));
|
|
407
|
+
}
|
|
408
|
+
return Promise.resolve(createSuccessResult({
|
|
409
|
+
message: `Todo "${input.todoId}" claimed by "${input.agentId}"`,
|
|
410
|
+
todo: {
|
|
411
|
+
id: updated.id,
|
|
412
|
+
content: updated.content,
|
|
413
|
+
status: updated.status,
|
|
414
|
+
owner: updated.owner,
|
|
415
|
+
},
|
|
416
|
+
}));
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
const todoHandoff = defineTool({
|
|
420
|
+
name: 'todo_handoff',
|
|
421
|
+
description: 'Hand off a SESSION todo (from CLI footer) to another agent. ' +
|
|
422
|
+
'For persistent backlog items, use workitem_handoff.',
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
todoId: {
|
|
427
|
+
type: 'string',
|
|
428
|
+
description: 'The todo ID to hand off',
|
|
429
|
+
},
|
|
430
|
+
toAgentId: {
|
|
431
|
+
type: 'string',
|
|
432
|
+
description: 'The agent ID to hand off to',
|
|
433
|
+
},
|
|
434
|
+
notes: {
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: 'Optional notes about the handoff (context, status, blockers)',
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
required: ['todoId', 'toAgentId'],
|
|
440
|
+
},
|
|
441
|
+
execute: (input) => {
|
|
442
|
+
const todos = todoStore.getAll();
|
|
443
|
+
const todo = todos.find((t) => t.id === input.todoId);
|
|
444
|
+
if (!todo) {
|
|
445
|
+
return Promise.resolve(createErrorResult(`Todo "${input.todoId}" not found`));
|
|
446
|
+
}
|
|
447
|
+
const previousOwner = todo.owner;
|
|
448
|
+
const updated = todoStore.update(input.todoId, { owner: input.toAgentId });
|
|
449
|
+
if (!updated) {
|
|
450
|
+
return Promise.resolve(createErrorResult(`Failed to update todo "${input.todoId}"`));
|
|
451
|
+
}
|
|
452
|
+
return Promise.resolve(createSuccessResult({
|
|
453
|
+
message: `Todo "${input.todoId}" handed off from "${previousOwner ?? 'unassigned'}" to "${input.toAgentId}"`,
|
|
454
|
+
todo: {
|
|
455
|
+
id: updated.id,
|
|
456
|
+
content: updated.content,
|
|
457
|
+
status: updated.status,
|
|
458
|
+
owner: updated.owner,
|
|
459
|
+
},
|
|
460
|
+
previousOwner,
|
|
461
|
+
notes: input.notes,
|
|
462
|
+
}));
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
return { todoWrite, todoRead, todoClaim, todoHandoff, store: todoStore };
|
|
315
466
|
}
|
|
316
467
|
/**
|
|
317
468
|
* Reset the default store (useful for testing)
|