@curie-agent/tools 0.4.2 → 0.4.4
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/.tsbuildinfo +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/read.d.ts.map +1 -1
- package/dist/src/read.js +7 -0
- package/dist/src/read.js.map +1 -1
- package/dist/src/reminder.d.ts +1 -1
- package/dist/src/reminder.d.ts.map +1 -1
- package/dist/src/reminder.js +6 -19
- package/dist/src/reminder.js.map +1 -1
- package/dist/src/spawn-agent.d.ts +11 -2
- package/dist/src/spawn-agent.d.ts.map +1 -1
- package/dist/src/spawn-agent.js +19 -6
- package/dist/src/spawn-agent.js.map +1 -1
- package/dist/src/task.d.ts +1 -1
- package/dist/src/task.d.ts.map +1 -1
- package/dist/src/task.js +6 -19
- package/dist/src/task.js.map +1 -1
- package/dist/src/todo.d.ts +8 -5
- package/dist/src/todo.d.ts.map +1 -1
- package/dist/src/todo.js +111 -287
- package/dist/src/todo.js.map +1 -1
- package/dist/src/tool.d.ts +6 -0
- package/dist/src/tool.d.ts.map +1 -1
- package/dist/src/tool.js +16 -0
- package/dist/src/tool.js.map +1 -1
- package/dist/src/web-fetch.d.ts +1 -0
- package/dist/src/web-fetch.d.ts.map +1 -1
- package/dist/src/web-fetch.js +17 -1
- package/dist/src/web-fetch.js.map +1 -1
- package/package.json +3 -3
package/dist/src/spawn-agent.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
const MAX_TEXT = 50_000;
|
|
1
2
|
export function createSpawnAgentTool(config) {
|
|
2
|
-
const { subagentExecutor,
|
|
3
|
+
const { subagentExecutor, resolve } = config;
|
|
3
4
|
return {
|
|
4
5
|
definition: {
|
|
5
6
|
name: 'spawn_agent',
|
|
6
|
-
description: 'Spawn a subagent to work on a task
|
|
7
|
+
description: 'Spawn a subagent to work on a task. The subagent runs with its own TurnLoop and streams output back to the parent session. Blocks until the subagent finishes and returns its final answer, so delegating research here keeps the intermediate tool results out of this session\'s context.',
|
|
7
8
|
inputSchema: JSON.stringify({
|
|
8
9
|
type: 'object',
|
|
9
10
|
required: ['prompt'],
|
|
@@ -50,6 +51,7 @@ export function createSpawnAgentTool(config) {
|
|
|
50
51
|
const modelOverride = input.model;
|
|
51
52
|
const providerName = input.provider;
|
|
52
53
|
try {
|
|
54
|
+
const { provider, cwd, settings, model, tools } = resolve();
|
|
53
55
|
// If a specific provider is requested, create a settings override
|
|
54
56
|
const spawnSettings = providerName
|
|
55
57
|
? { ...settings, current_provider: providerName }
|
|
@@ -67,12 +69,23 @@ export function createSpawnAgentTool(config) {
|
|
|
67
69
|
allowedTools: allowedToolNames,
|
|
68
70
|
type: 'subagent',
|
|
69
71
|
});
|
|
72
|
+
// Await completion so the parent model actually receives the answer —
|
|
73
|
+
// without this, delegating work to a subagent returns nothing usable.
|
|
74
|
+
const final = (await subagentExecutor.waitFor(handle.agentId)) ?? handle;
|
|
75
|
+
let text = final.text;
|
|
76
|
+
if (text.length > MAX_TEXT) {
|
|
77
|
+
text =
|
|
78
|
+
text.slice(0, MAX_TEXT) +
|
|
79
|
+
`\n...[truncated at ${String(MAX_TEXT / 1000)}k chars of ${String(text.length)} — the full output is in subagent session ${final.sessionId}]`;
|
|
80
|
+
}
|
|
70
81
|
return {
|
|
71
82
|
output: {
|
|
72
|
-
agentId:
|
|
73
|
-
|
|
74
|
-
status:
|
|
75
|
-
|
|
83
|
+
agentId: final.agentId,
|
|
84
|
+
sessionId: final.sessionId,
|
|
85
|
+
status: final.status,
|
|
86
|
+
text,
|
|
87
|
+
toolCalls: final.toolCalls,
|
|
88
|
+
errors: final.errors,
|
|
76
89
|
},
|
|
77
90
|
};
|
|
78
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-agent.js","sourceRoot":"","sources":["../../src/spawn-agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spawn-agent.js","sourceRoot":"","sources":["../../src/spawn-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/src/task.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CreateScheduledTask tool — schedules the LLM agent to execute an instruction at a specific time.
|
|
3
|
-
*
|
|
3
|
+
* Writes through the process-shared TaskManager (mode='agent').
|
|
4
4
|
*/
|
|
5
5
|
export declare const scheduledTaskTool: import("./tool.js").Tool & {
|
|
6
6
|
validate: (raw: Record<string, unknown>) => {
|
package/dist/src/task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/task.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/task.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,eAAO,MAAM,iBAAiB;;;;;CA8B7B,CAAC"}
|
package/dist/src/task.js
CHANGED
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CreateScheduledTask tool — schedules the LLM agent to execute an instruction at a specific time.
|
|
3
|
-
*
|
|
3
|
+
* Writes through the process-shared TaskManager (mode='agent').
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import { createTool } from './tool.js';
|
|
7
|
-
import {
|
|
8
|
-
const taskManager = new TaskManager();
|
|
6
|
+
import { createTool, ensureTimezoneOffset } from './tool.js';
|
|
7
|
+
import { getTaskManager } from '@curie-agent/core';
|
|
9
8
|
const CreateScheduledTaskSchema = z.object({
|
|
10
9
|
instruction: z.string().describe('What the agent should do when the scheduled task executes'),
|
|
11
10
|
scheduled_at: z.string().describe('ISO 8601 datetime with timezone offset (e.g. "2026-05-03T07:55:00+02:00")'),
|
|
12
11
|
});
|
|
13
|
-
export const scheduledTaskTool = createTool('CreateScheduledTask', 'Schedules a task for the agent to autonomously execute at a specified time. The agent will run the instruction using available tools and deliver results.', CreateScheduledTaskSchema, async (input,
|
|
14
|
-
|
|
15
|
-
const hasTimezone = /[Zz]|[+-]\d{2}(:?\d{2})?$/.test(dateStr);
|
|
16
|
-
if (!hasTimezone) {
|
|
17
|
-
const date = new Date();
|
|
18
|
-
const offsetMinutes = -date.getTimezoneOffset();
|
|
19
|
-
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
20
|
-
const absMinutes = Math.abs(offsetMinutes);
|
|
21
|
-
const hours = String(Math.floor(absMinutes / 60)).padStart(2, '0');
|
|
22
|
-
const mins = String(absMinutes % 60).padStart(2, '0');
|
|
23
|
-
dateStr = `${dateStr}${sign}:${mins}`;
|
|
24
|
-
}
|
|
25
|
-
const scheduledAt = new Date(dateStr).getTime();
|
|
12
|
+
export const scheduledTaskTool = createTool('CreateScheduledTask', 'Schedules a task for the agent to autonomously execute at a specified time. The agent will run the instruction using available tools and deliver results.', CreateScheduledTaskSchema, async (input, _ctx) => {
|
|
13
|
+
const scheduledAt = new Date(ensureTimezoneOffset(input.scheduled_at)).getTime();
|
|
26
14
|
if (isNaN(scheduledAt)) {
|
|
27
15
|
return {
|
|
28
16
|
output: null,
|
|
29
17
|
error: `Invalid datetime: "${input.scheduled_at}". Use ISO 8601 format with timezone offset.`,
|
|
30
18
|
};
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
const task = taskManager.create({
|
|
20
|
+
const task = getTaskManager().create({
|
|
34
21
|
title: input.instruction,
|
|
35
22
|
mode: 'agent',
|
|
36
23
|
scope: 'personal',
|
package/dist/src/task.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/task.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/task.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAoB,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2DAA2D,CAAC;IAC7F,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2EAA2E,CAAC;CAC/G,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CACzC,qBAAqB,EACrB,2JAA2J,EAC3J,yBAAyB,EACzB,KAAK,EAAE,KAAK,EAAE,IAAiB,EAAE,EAAE;IACjC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACjF,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,sBAAsB,KAAK,CAAC,YAAY,8CAA8C;SAC9F,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC,MAAM,CAAC;QACnC,KAAK,EAAE,KAAK,CAAC,WAAW;QACxB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,UAAU;QACjB,YAAY,EAAE,WAAW;KAC1B,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE;YACN,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,GAAG;YACnF,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB;KACF,CAAC;AACJ,CAAC,CACF,CAAC"}
|
package/dist/src/todo.d.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todo tool — manages structured task lists.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* All reads and writes go through the process-shared TaskManager, so this tool,
|
|
5
|
+
* the daemon scheduler, and the `todo.*` RPC handlers can never overwrite each
|
|
6
|
+
* other. Legacy `todo.json` files are still honoured when present; new stores
|
|
7
|
+
* are created as `tasks.json`.
|
|
5
8
|
*/
|
|
6
9
|
export declare const todoTool: import("./tool.js").Tool & {
|
|
7
10
|
validate: (raw: Record<string, unknown>) => {
|
|
8
|
-
action: "
|
|
11
|
+
action: "edit" | "list" | "add" | "remove" | "complete" | "cancel" | "start" | "reorder";
|
|
9
12
|
scope: "personal" | "project";
|
|
10
13
|
id?: string | undefined;
|
|
11
14
|
title?: string | undefined;
|
|
12
15
|
description?: string | undefined;
|
|
13
|
-
status?: "
|
|
16
|
+
status?: "done" | "backlog" | "todo" | "in_progress" | "canceled" | undefined;
|
|
14
17
|
priority?: "low" | "medium" | "high" | "critical" | undefined;
|
|
15
18
|
tags?: string[] | undefined;
|
|
16
19
|
ids?: string[] | undefined;
|
|
17
|
-
filter_status?: "
|
|
20
|
+
filter_status?: "done" | "backlog" | "todo" | "in_progress" | "canceled" | undefined;
|
|
18
21
|
filter_priority?: "low" | "medium" | "high" | "critical" | undefined;
|
|
19
22
|
};
|
|
20
23
|
};
|
package/dist/src/todo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"todo.d.ts","sourceRoot":"","sources":["../../src/todo.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"todo.d.ts","sourceRoot":"","sources":["../../src/todo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;CAkFpB,CAAC"}
|
package/dist/src/todo.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todo tool — manages structured task lists.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* All reads and writes go through the process-shared TaskManager, so this tool,
|
|
5
|
+
* the daemon scheduler, and the `todo.*` RPC handlers can never overwrite each
|
|
6
|
+
* other. Legacy `todo.json` files are still honoured when present; new stores
|
|
7
|
+
* are created as `tasks.json`.
|
|
5
8
|
*/
|
|
6
9
|
import fs from 'node:fs';
|
|
7
10
|
import path from 'node:path';
|
|
8
11
|
import { z } from 'zod';
|
|
9
12
|
import { createTool, expandPath } from './tool.js';
|
|
13
|
+
import { getTaskManager, repairTaskShapes } from '@curie-agent/core';
|
|
10
14
|
// ---------------------------------------------------------------------------
|
|
11
|
-
// Zod schema
|
|
15
|
+
// Zod schema
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
const ACTION_VALUES = ['list', 'add', 'edit', 'remove', 'complete', 'cancel', 'start', 'reorder'];
|
|
14
18
|
const TodoSchema = z.object({
|
|
15
19
|
action: z.enum(ACTION_VALUES).describe('Action: list, add, edit, remove, complete, cancel, start, reorder'),
|
|
16
20
|
scope: z.enum(['personal', 'project']).describe('Scope: personal (~/.curie-agent/tasks.json) or project (<cwd>/tasks.json)'),
|
|
17
|
-
id: z.string().optional().describe('Task ID (required for edit, remove, complete, cancel, start
|
|
21
|
+
id: z.string().optional().describe('Task ID or unique ID prefix (required for edit, remove, complete, cancel, start)'),
|
|
18
22
|
title: z.string().optional().describe('Task title (required for add, optional for edit)'),
|
|
19
23
|
description: z.string().optional().describe('Task description (optional)'),
|
|
20
24
|
status: z.enum(['backlog', 'todo', 'in_progress', 'done', 'canceled']).optional().describe('Task status'),
|
|
@@ -26,297 +30,117 @@ const TodoSchema = z.object({
|
|
|
26
30
|
filter_priority: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by priority'),
|
|
27
31
|
}).strict();
|
|
28
32
|
// ---------------------------------------------------------------------------
|
|
29
|
-
//
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
/** Ensure a task has all UnifiedTask fields (migrates legacy format on read). */
|
|
32
|
-
function normalizeTask(t) {
|
|
33
|
-
if (!t || typeof t !== 'object')
|
|
34
|
-
return {};
|
|
35
|
-
const obj = t;
|
|
36
|
-
const validStatuses = ['backlog', 'todo', 'in_progress', 'done', 'canceled', 'pending'];
|
|
37
|
-
const rawStatus = String(obj.status ?? '');
|
|
38
|
-
const status = (validStatuses.includes(rawStatus) ? rawStatus : 'todo');
|
|
39
|
-
const validModes = ['human', 'agent', 'notify'];
|
|
40
|
-
const rawMode = String(obj.mode ?? '');
|
|
41
|
-
const mode = (validModes.includes(rawMode) ? rawMode : 'human');
|
|
42
|
-
const validPriority = ['low', 'medium', 'high', 'critical'];
|
|
43
|
-
const rawPriority = String(obj.priority ?? 'medium');
|
|
44
|
-
const priority = validPriority.includes(rawPriority) ? rawPriority : 'medium';
|
|
45
|
-
let frequency = null;
|
|
46
|
-
if (obj.frequency && typeof obj.frequency === 'object' && 'type' in obj.frequency && 'value' in obj.frequency) {
|
|
47
|
-
const rawType = String(obj.frequency.type);
|
|
48
|
-
const validSchedules = ['intraday', 'daily', 'weekly', 'monthly', 'dreaming'];
|
|
49
|
-
if (validSchedules.includes(rawType)) {
|
|
50
|
-
frequency = { type: rawType, value: String(obj.frequency.value ?? '') };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
id: String(obj.id ?? ''),
|
|
55
|
-
title: String(obj.title ?? ''),
|
|
56
|
-
description: String(obj.description ?? ''),
|
|
57
|
-
status,
|
|
58
|
-
priority,
|
|
59
|
-
tags: Array.isArray(obj.tags) ? obj.tags : [],
|
|
60
|
-
mode,
|
|
61
|
-
scheduled_at: typeof obj.scheduled_at === 'number' ? obj.scheduled_at : undefined,
|
|
62
|
-
frequency,
|
|
63
|
-
result: typeof obj.result === 'string' ? obj.result : undefined,
|
|
64
|
-
scope: String(obj.scope) === 'project' ? 'project' : 'personal',
|
|
65
|
-
order: typeof obj.order === 'number' ? obj.order : 0,
|
|
66
|
-
created_at: String(obj.created_at ?? new Date().toISOString()),
|
|
67
|
-
completed_at: obj.completed_at && obj.completed_at !== null ? String(obj.completed_at) : undefined,
|
|
68
|
-
last_run: typeof obj.last_run === 'number' ? obj.last_run : undefined,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// File I/O (supports both old todo.json and new tasks.json)
|
|
33
|
+
// Store resolution
|
|
73
34
|
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Resolve which file backs a scope. Prefers the unified `tasks.json`, falls
|
|
37
|
+
* back to an existing legacy `todo.json`, and defaults new stores to
|
|
38
|
+
* `tasks.json`.
|
|
39
|
+
*/
|
|
74
40
|
function resolveScopePath(scope, cwd) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (fs.existsSync(projectPath))
|
|
84
|
-
return projectPath;
|
|
85
|
-
return path.join(cwd, 'todo.json');
|
|
86
|
-
}
|
|
87
|
-
function readTaskFile(filePath) {
|
|
88
|
-
if (!fs.existsSync(filePath))
|
|
89
|
-
return null;
|
|
90
|
-
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(raw);
|
|
93
|
-
if (typeof parsed === 'object' && parsed !== null && 'tasks' in parsed && Array.isArray(parsed.tasks)) {
|
|
94
|
-
return parsed;
|
|
95
|
-
}
|
|
96
|
-
// Legacy format without $schema/version — wrap it
|
|
97
|
-
if (typeof parsed === 'object' && parsed !== null && 'tasks' in parsed) {
|
|
98
|
-
const tasks = parsed.tasks;
|
|
99
|
-
return { $schema: 'legacy.schema.json', version: 1, tasks: Array.isArray(tasks) ? tasks : [] };
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return null; // corrupted → reset
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function ensureTaskFile(filePath) {
|
|
108
|
-
if (!fs.existsSync(filePath)) {
|
|
109
|
-
const fresh = { $schema: 'tasks.schema.json', version: 1, tasks: [] };
|
|
110
|
-
fs.writeFileSync(filePath, JSON.stringify(fresh, null, 2), 'utf-8');
|
|
111
|
-
return fresh;
|
|
112
|
-
}
|
|
113
|
-
const data = readTaskFile(filePath);
|
|
114
|
-
if (!data) {
|
|
115
|
-
const fresh = { $schema: 'tasks.schema.json', version: 1, tasks: [] };
|
|
116
|
-
fs.writeFileSync(filePath, JSON.stringify(fresh, null, 2), 'utf-8');
|
|
117
|
-
return fresh;
|
|
118
|
-
}
|
|
119
|
-
// Normalize existing tasks to UnifiedTask format
|
|
120
|
-
const normalized = data.tasks.map(normalizeTask);
|
|
121
|
-
if (normalized.length !== data.tasks.length || JSON.stringify(normalized) !== JSON.stringify(data.tasks)) {
|
|
122
|
-
data.tasks = normalized;
|
|
123
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
124
|
-
}
|
|
125
|
-
return data;
|
|
41
|
+
const dir = scope === 'personal' ? path.join(expandPath('~'), '.curie-agent') : cwd;
|
|
42
|
+
const unified = path.join(dir, 'tasks.json');
|
|
43
|
+
if (fs.existsSync(unified))
|
|
44
|
+
return unified;
|
|
45
|
+
const legacy = path.join(dir, 'todo.json');
|
|
46
|
+
if (fs.existsSync(legacy))
|
|
47
|
+
return legacy;
|
|
48
|
+
return unified;
|
|
126
49
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
50
|
+
/** Open the shared manager for a scope, repairing legacy shapes first. */
|
|
51
|
+
function openStore(scope, cwd) {
|
|
52
|
+
const filePath = resolveScopePath(scope, cwd);
|
|
53
|
+
repairTaskShapes(filePath);
|
|
54
|
+
const manager = getTaskManager(filePath);
|
|
55
|
+
manager.load();
|
|
56
|
+
return manager;
|
|
132
57
|
}
|
|
58
|
+
const NOT_FOUND = (id) => `Task with id "${id}" not found`;
|
|
133
59
|
// ---------------------------------------------------------------------------
|
|
134
60
|
// Tool implementation
|
|
135
61
|
// ---------------------------------------------------------------------------
|
|
136
62
|
export const todoTool = createTool('Todo', 'Manages a structured JSON-based task list with add, edit, remove, complete, cancel, start, reorder, and list actions. Tasks can be manual (todo list), agent (LLM executes at scheduled time), or notify (reminder notification only). Defaults to manual mode unless a time is specified.', TodoSchema, async (input, ctx) => {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
tasks = tasks.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (input.action === 'remove') {
|
|
219
|
-
const data = ensureTaskFile(filePath);
|
|
220
|
-
let tasks;
|
|
221
|
-
try {
|
|
222
|
-
tasks = data.tasks.map(normalizeTask);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return { output: null, error: 'Failed to parse task file.' };
|
|
226
|
-
}
|
|
227
|
-
if (!input.id)
|
|
228
|
-
return { output: null, error: 'ID is required for remove action' };
|
|
229
|
-
const idx = tasks.findIndex((t) => t.id === input.id);
|
|
230
|
-
if (idx === -1)
|
|
231
|
-
return { output: null, error: `Task with id "${input.id}" not found` };
|
|
232
|
-
tasks.splice(idx, 1);
|
|
233
|
-
tasks.forEach((t, i) => { t.order = i; });
|
|
234
|
-
data.tasks = tasks;
|
|
235
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
236
|
-
return { output: { success: true, id: input.id } };
|
|
237
|
-
}
|
|
238
|
-
if (input.action === 'complete') {
|
|
239
|
-
const data = ensureTaskFile(filePath);
|
|
240
|
-
let tasks;
|
|
241
|
-
try {
|
|
242
|
-
tasks = data.tasks.map(normalizeTask);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return { output: null, error: 'Failed to parse task file.' };
|
|
246
|
-
}
|
|
247
|
-
if (!input.id)
|
|
248
|
-
return { output: null, error: 'ID is required for complete action' };
|
|
249
|
-
const idx = tasks.findIndex((t) => t.id === input.id);
|
|
250
|
-
if (idx === -1)
|
|
251
|
-
return { output: null, error: `Task with id "${input.id}" not found` };
|
|
252
|
-
const task = tasks[idx];
|
|
253
|
-
task.status = 'done';
|
|
254
|
-
task.completed_at = formatDate();
|
|
255
|
-
data.tasks[idx] = task;
|
|
256
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
257
|
-
return { output: task };
|
|
258
|
-
}
|
|
259
|
-
if (input.action === 'cancel') {
|
|
260
|
-
const data = ensureTaskFile(filePath);
|
|
261
|
-
let tasks;
|
|
262
|
-
try {
|
|
263
|
-
tasks = data.tasks.map(normalizeTask);
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
return { output: null, error: 'Failed to parse task file.' };
|
|
267
|
-
}
|
|
268
|
-
if (!input.id)
|
|
269
|
-
return { output: null, error: 'ID is required for cancel action' };
|
|
270
|
-
const idx = tasks.findIndex((t) => t.id === input.id);
|
|
271
|
-
if (idx === -1)
|
|
272
|
-
return { output: null, error: `Task with id "${input.id}" not found` };
|
|
273
|
-
tasks[idx].status = 'canceled';
|
|
274
|
-
data.tasks[idx] = tasks[idx];
|
|
275
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
276
|
-
return { output: tasks[idx] };
|
|
277
|
-
}
|
|
278
|
-
if (input.action === 'start') {
|
|
279
|
-
const data = ensureTaskFile(filePath);
|
|
280
|
-
let tasks;
|
|
281
|
-
try {
|
|
282
|
-
tasks = data.tasks.map(normalizeTask);
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
return { output: null, error: 'Failed to parse task file.' };
|
|
286
|
-
}
|
|
287
|
-
if (!input.id)
|
|
288
|
-
return { output: null, error: 'ID is required for start action' };
|
|
289
|
-
const idx = tasks.findIndex((t) => t.id === input.id);
|
|
290
|
-
if (idx === -1)
|
|
291
|
-
return { output: null, error: `Task with id "${input.id}" not found` };
|
|
292
|
-
tasks[idx].status = 'in_progress';
|
|
293
|
-
data.tasks[idx] = tasks[idx];
|
|
294
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
295
|
-
return { output: tasks[idx] };
|
|
296
|
-
}
|
|
297
|
-
if (input.action === 'reorder') {
|
|
298
|
-
const data = ensureTaskFile(filePath);
|
|
299
|
-
let tasks;
|
|
300
|
-
try {
|
|
301
|
-
tasks = data.tasks.map(normalizeTask);
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return { output: null, error: 'Failed to parse task file.' };
|
|
305
|
-
}
|
|
306
|
-
const ids = input.ids;
|
|
307
|
-
if (!ids || !ids.length)
|
|
308
|
-
return { output: null, error: 'IDs array is required for reorder action' };
|
|
309
|
-
const existingIds = new Set(tasks.map((t) => t.id));
|
|
310
|
-
const validIds = ids.filter((id) => existingIds.has(id));
|
|
311
|
-
if (validIds.length !== tasks.length) {
|
|
312
|
-
return { output: null, error: `Not all task IDs exist. Missing: ${ids.filter((id) => !existingIds.has(id)).join(', ')}` };
|
|
313
|
-
}
|
|
314
|
-
tasks.sort((a, b) => validIds.indexOf(a.id) - validIds.indexOf(b.id));
|
|
315
|
-
tasks.forEach((t, i) => { t.order = i; });
|
|
316
|
-
data.tasks = tasks;
|
|
317
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
318
|
-
return { output: { reordered: true, count: validIds.length } };
|
|
63
|
+
const store = openStore(input.scope, ctx.cwd);
|
|
64
|
+
switch (input.action) {
|
|
65
|
+
case 'list': {
|
|
66
|
+
let tasks = store.list({
|
|
67
|
+
status: input.filter_status,
|
|
68
|
+
priority: input.filter_priority,
|
|
69
|
+
});
|
|
70
|
+
if (input.tags && input.tags.length > 0) {
|
|
71
|
+
const wanted = input.tags;
|
|
72
|
+
tasks = tasks.filter((t) => wanted.every((tag) => t.tags.includes(tag)));
|
|
73
|
+
}
|
|
74
|
+
tasks = [...tasks].sort((a, b) => a.order - b.order);
|
|
75
|
+
return { output: tasks };
|
|
76
|
+
}
|
|
77
|
+
case 'add': {
|
|
78
|
+
if (!input.title)
|
|
79
|
+
return { output: null, error: 'Title is required for add action' };
|
|
80
|
+
const task = store.create({
|
|
81
|
+
title: input.title,
|
|
82
|
+
description: input.description,
|
|
83
|
+
mode: 'human',
|
|
84
|
+
scope: input.scope,
|
|
85
|
+
priority: input.priority,
|
|
86
|
+
tags: input.tags,
|
|
87
|
+
});
|
|
88
|
+
return { output: task };
|
|
89
|
+
}
|
|
90
|
+
case 'edit': {
|
|
91
|
+
if (!input.id)
|
|
92
|
+
return { output: null, error: 'ID is required for edit action' };
|
|
93
|
+
const patch = {};
|
|
94
|
+
if (input.title !== undefined)
|
|
95
|
+
patch.title = input.title;
|
|
96
|
+
if (input.description !== undefined)
|
|
97
|
+
patch.description = input.description;
|
|
98
|
+
if (input.status !== undefined)
|
|
99
|
+
patch.status = input.status;
|
|
100
|
+
if (input.priority !== undefined)
|
|
101
|
+
patch.priority = input.priority;
|
|
102
|
+
if (input.tags !== undefined)
|
|
103
|
+
patch.tags = input.tags;
|
|
104
|
+
const updated = store.updateTask(input.id, patch);
|
|
105
|
+
if (!updated)
|
|
106
|
+
return { output: null, error: NOT_FOUND(input.id) };
|
|
107
|
+
return { output: updated };
|
|
108
|
+
}
|
|
109
|
+
case 'remove': {
|
|
110
|
+
if (!input.id)
|
|
111
|
+
return { output: null, error: 'ID is required for remove action' };
|
|
112
|
+
const task = store.findTask(input.id);
|
|
113
|
+
if (!task)
|
|
114
|
+
return { output: null, error: NOT_FOUND(input.id) };
|
|
115
|
+
const removedId = task.id;
|
|
116
|
+
store.removeTask(removedId);
|
|
117
|
+
return { output: { success: true, id: removedId } };
|
|
118
|
+
}
|
|
119
|
+
case 'complete':
|
|
120
|
+
case 'cancel':
|
|
121
|
+
case 'start': {
|
|
122
|
+
if (!input.id)
|
|
123
|
+
return { output: null, error: `ID is required for ${input.action} action` };
|
|
124
|
+
const task = store.findTask(input.id);
|
|
125
|
+
if (!task)
|
|
126
|
+
return { output: null, error: NOT_FOUND(input.id) };
|
|
127
|
+
const nextStatus = input.action === 'complete' ? 'done' : input.action === 'cancel' ? 'canceled' : 'in_progress';
|
|
128
|
+
store.updateTaskStatus(task.id, nextStatus);
|
|
129
|
+
return { output: store.findTask(task.id) };
|
|
130
|
+
}
|
|
131
|
+
case 'reorder': {
|
|
132
|
+
const ids = input.ids;
|
|
133
|
+
if (!ids || ids.length === 0)
|
|
134
|
+
return { output: null, error: 'IDs array is required for reorder action' };
|
|
135
|
+
const missing = ids.filter((id) => !store.findTask(id));
|
|
136
|
+
if (missing.length > 0) {
|
|
137
|
+
return { output: null, error: `Not all task IDs exist. Missing: ${missing.join(', ')}` };
|
|
138
|
+
}
|
|
139
|
+
const count = store.reorder(ids);
|
|
140
|
+
return { output: { reordered: true, count } };
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return { output: null, error: `Unknown action: ${String(input.action)}` };
|
|
319
144
|
}
|
|
320
|
-
return { output: null, error: `Unknown action: ${input.action}` };
|
|
321
145
|
});
|
|
322
146
|
//# sourceMappingURL=todo.js.map
|