@curie-agent/core 0.4.3 → 0.4.5
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 +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/reminder-parser.d.ts +6 -0
- package/dist/src/reminder-parser.d.ts.map +1 -1
- package/dist/src/reminder-parser.js +85 -105
- package/dist/src/reminder-parser.js.map +1 -1
- package/dist/src/task-manager.d.ts +53 -12
- package/dist/src/task-manager.d.ts.map +1 -1
- package/dist/src/task-manager.js +225 -124
- package/dist/src/task-manager.js.map +1 -1
- package/dist/src/task-migration.d.ts +29 -2
- package/dist/src/task-migration.d.ts.map +1 -1
- package/dist/src/task-migration.js +115 -7
- package/dist/src/task-migration.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,73 +2,62 @@
|
|
|
2
2
|
* Zero-dependency natural language time parser for reminders.
|
|
3
3
|
* Parses patterns like "in 30 minutes", "tomorrow at 7am", "today at 19:00",
|
|
4
4
|
* "next monday at 9am", etc.
|
|
5
|
+
*
|
|
6
|
+
* Two rules the callers depend on:
|
|
7
|
+
* - A parsed time is never in the past. A reminder that fires the instant it
|
|
8
|
+
* is created is worse than no reminder.
|
|
9
|
+
* - An unrecognised input returns null rather than guessing. Silently
|
|
10
|
+
* defaulting produces reminders at times the user never asked for.
|
|
5
11
|
*/
|
|
6
12
|
const WEEKDAYS = [
|
|
7
13
|
'sunday', 'monday', 'tuesday', 'wednesday',
|
|
8
14
|
'thursday', 'friday', 'saturday',
|
|
9
15
|
];
|
|
10
16
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
17
|
+
* Pull a time-of-day off the *start* of a string, returning it plus the
|
|
18
|
+
* remaining message text. Anchored deliberately: an unanchored match lets a
|
|
19
|
+
* digit anywhere in the sentence become the hour, so "tomorrow buy 3 apples"
|
|
20
|
+
* silently became "apples" at 03:00.
|
|
13
21
|
*/
|
|
14
|
-
function
|
|
15
|
-
const match =
|
|
22
|
+
function extractLeadingTime(str) {
|
|
23
|
+
const match = /^(\d{1,2})(?::(\d{2}))?\s*(?:([ap])\.?m\.?)?\b\s*(.*)$/i.exec(str);
|
|
16
24
|
if (!match)
|
|
17
25
|
return null;
|
|
18
|
-
let hours = parseInt(match[1] ?? '
|
|
26
|
+
let hours = parseInt(match[1] ?? '', 10);
|
|
19
27
|
const minutes = match[2] ? parseInt(match[2], 10) : 0;
|
|
20
|
-
const
|
|
21
|
-
if (ampm) {
|
|
22
|
-
const isPM = ampm.toLowerCase().startsWith('p');
|
|
23
|
-
if (isPM && hours < 12)
|
|
24
|
-
hours += 12;
|
|
25
|
-
if (!isPM && hours === 12)
|
|
26
|
-
hours = 0;
|
|
27
|
-
}
|
|
28
|
-
return { hours, minutes };
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Extract a time-of-day from the beginning of a string, returning the time
|
|
32
|
-
* and the remaining message text. e.g. "7:00 am make breakfast" →
|
|
33
|
-
* { time: {hours:7, minutes:0}, message: "make breakfast" }
|
|
34
|
-
*/
|
|
35
|
-
function extractTimeAndMessage(str) {
|
|
36
|
-
const match = str.match(/(\d{1,2}):?(\d{2})?\s*([ap]m)?\s*(.*)/i);
|
|
37
|
-
if (!match)
|
|
38
|
-
return null;
|
|
39
|
-
let hours = parseInt(match[1] ?? '0', 10);
|
|
40
|
-
const minutes = match[2] ? parseInt(match[2], 10) : 0;
|
|
41
|
-
const ampm = match[3];
|
|
28
|
+
const meridiem = match[3];
|
|
42
29
|
const message = (match[4] ?? '').trim();
|
|
43
|
-
if (
|
|
44
|
-
const isPM =
|
|
30
|
+
if (meridiem) {
|
|
31
|
+
const isPM = meridiem.toLowerCase() === 'p';
|
|
45
32
|
if (isPM && hours < 12)
|
|
46
33
|
hours += 12;
|
|
47
34
|
if (!isPM && hours === 12)
|
|
48
35
|
hours = 0;
|
|
49
36
|
}
|
|
37
|
+
if (!Number.isInteger(hours) || hours < 0 || hours > 23)
|
|
38
|
+
return null;
|
|
39
|
+
if (!Number.isInteger(minutes) || minutes < 0 || minutes > 59)
|
|
40
|
+
return null;
|
|
50
41
|
return { time: { hours, minutes }, message };
|
|
51
42
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Apply a time-of-day to a base date, returning a new Date.
|
|
54
|
-
*/
|
|
43
|
+
/** Apply a time-of-day to a base date, returning a new Date. */
|
|
55
44
|
function applyTime(date, hours, minutes) {
|
|
56
45
|
const result = new Date(date);
|
|
57
46
|
result.setHours(hours, minutes, 0, 0);
|
|
58
47
|
return result;
|
|
59
48
|
}
|
|
60
49
|
/**
|
|
61
|
-
* Get the next occurrence of a weekday at a given time
|
|
62
|
-
*
|
|
50
|
+
* Get the next occurrence of a weekday at a given time. Always forward —
|
|
51
|
+
* "next friday" on a Friday means the Friday after this one.
|
|
63
52
|
*/
|
|
64
53
|
function nextWeekday(dayName, baseDate, time) {
|
|
65
54
|
const targetDay = WEEKDAYS.indexOf(dayName.toLowerCase());
|
|
66
55
|
if (targetDay === -1)
|
|
67
|
-
return
|
|
56
|
+
return null;
|
|
68
57
|
const today = baseDate.getDay();
|
|
69
58
|
let diff = targetDay - today;
|
|
70
59
|
if (diff <= 0)
|
|
71
|
-
diff += 7;
|
|
60
|
+
diff += 7;
|
|
72
61
|
const result = new Date(baseDate);
|
|
73
62
|
result.setDate(result.getDate() + diff);
|
|
74
63
|
return applyTime(result, time.hours, time.minutes);
|
|
@@ -78,87 +67,78 @@ export function parseReminderTime(input) {
|
|
|
78
67
|
if (!trimmed)
|
|
79
68
|
return null;
|
|
80
69
|
const now = new Date();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Pattern 1: "in X minutes"
|
|
84
|
-
const inMinMatch = remaining.match(/^in\s+(\d+)\s+minutes?/i);
|
|
70
|
+
// --- Relative offsets: an exact instant, no time-of-day involved --------
|
|
71
|
+
const inMinMatch = /^in\s+(\d+)\s+min(?:ute)?s?\b\s*(.*)$/i.exec(trimmed);
|
|
85
72
|
if (inMinMatch) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
const message = (inMinMatch[2] ?? '').trim();
|
|
74
|
+
if (!message)
|
|
75
|
+
return null;
|
|
76
|
+
return { message, scheduledAt: now.getTime() + parseInt(inMinMatch[1] ?? '0', 10) * 60_000 };
|
|
90
77
|
}
|
|
91
|
-
|
|
92
|
-
const inHourMatch = remaining.match(/^in\s+(\d+)\s+hours?/i);
|
|
78
|
+
const inHourMatch = /^in\s+(\d+)\s+(?:hours?|hrs?)\b\s*(.*)$/i.exec(trimmed);
|
|
93
79
|
if (inHourMatch) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
const message = (inHourMatch[2] ?? '').trim();
|
|
81
|
+
if (!message)
|
|
82
|
+
return null;
|
|
83
|
+
return { message, scheduledAt: now.getTime() + parseInt(inHourMatch[1] ?? '0', 10) * 3_600_000 };
|
|
98
84
|
}
|
|
99
|
-
//
|
|
100
|
-
const
|
|
85
|
+
// --- A calendar day, optionally followed by a time-of-day --------------
|
|
86
|
+
const baseDate = new Date(now);
|
|
87
|
+
let rest;
|
|
88
|
+
/** Whether a past time-of-day may roll into tomorrow. */
|
|
89
|
+
let allowRollForward = false;
|
|
90
|
+
/** Time-of-day to use when the input names a day but no clock time. */
|
|
91
|
+
let defaultTime = null;
|
|
92
|
+
let weekday = null;
|
|
93
|
+
const inDayMatch = /^in\s+(\d+)\s+days?\b\s*(?:at\s+)?(.*)$/i.exec(trimmed);
|
|
94
|
+
const tomorrowMatch = /^tomorrow\b\s*(?:at\s+)?(.*)$/i.exec(trimmed);
|
|
95
|
+
const todayMatch = /^(?:today|tonight)\b\s*(?:at\s+)?(.*)$/i.exec(trimmed);
|
|
96
|
+
const nextDayMatch = /^next\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b\s*(?:at\s+)?(.*)$/i.exec(trimmed);
|
|
97
|
+
const atMatch = /^at\s+(.*)$/i.exec(trimmed);
|
|
101
98
|
if (inDayMatch) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
tomorrow.setDate(tomorrow.getDate() + days);
|
|
106
|
-
tomorrow.setHours(0, 0, 0, 0);
|
|
107
|
-
targetTime = tomorrow;
|
|
108
|
-
remaining = msg;
|
|
99
|
+
baseDate.setDate(baseDate.getDate() + parseInt(inDayMatch[1] ?? '0', 10));
|
|
100
|
+
rest = inDayMatch[2] ?? '';
|
|
101
|
+
defaultTime = { hours: 0, minutes: 0 };
|
|
109
102
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const rest = (tomorrowMatch[1] ?? '').trim();
|
|
114
|
-
const parsed = extractTimeAndMessage(rest);
|
|
115
|
-
if (parsed) {
|
|
116
|
-
const tomorrow = new Date(now);
|
|
117
|
-
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
118
|
-
targetTime = applyTime(tomorrow, parsed.time.hours, parsed.time.minutes);
|
|
119
|
-
remaining = parsed.message;
|
|
120
|
-
}
|
|
103
|
+
else if (tomorrowMatch) {
|
|
104
|
+
baseDate.setDate(baseDate.getDate() + 1);
|
|
105
|
+
rest = tomorrowMatch[1] ?? '';
|
|
121
106
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const parsed = extractTimeAndMessage(rest);
|
|
127
|
-
if (parsed) {
|
|
128
|
-
targetTime = applyTime(now, parsed.time.hours, parsed.time.minutes);
|
|
129
|
-
remaining = parsed.message;
|
|
130
|
-
}
|
|
107
|
+
else if (todayMatch) {
|
|
108
|
+
rest = todayMatch[1] ?? '';
|
|
109
|
+
// "today at 6:00" typed at 10:00 can't mean the past — take the next 06:00.
|
|
110
|
+
allowRollForward = true;
|
|
131
111
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const dayName = (nextDayMatch[1] ?? '').trim();
|
|
136
|
-
const rest = (nextDayMatch[2] ?? '').trim();
|
|
137
|
-
const parsed = extractTimeAndMessage(rest);
|
|
138
|
-
if (parsed) {
|
|
139
|
-
targetTime = nextWeekday(dayName, now, parsed.time);
|
|
140
|
-
remaining = parsed.message;
|
|
141
|
-
}
|
|
112
|
+
else if (nextDayMatch) {
|
|
113
|
+
weekday = nextDayMatch[1] ?? '';
|
|
114
|
+
rest = nextDayMatch[2] ?? '';
|
|
142
115
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const rest = (atMatch[1] ?? '').trim();
|
|
147
|
-
const parsed = extractTimeAndMessage(rest);
|
|
148
|
-
if (parsed) {
|
|
149
|
-
targetTime = applyTime(now, parsed.time.hours, parsed.time.minutes);
|
|
150
|
-
remaining = parsed.message;
|
|
151
|
-
}
|
|
116
|
+
else if (atMatch) {
|
|
117
|
+
rest = atMatch[1] ?? '';
|
|
118
|
+
allowRollForward = true;
|
|
152
119
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
120
|
+
else {
|
|
121
|
+
// No recognisable time reference — don't invent one.
|
|
122
|
+
return null;
|
|
156
123
|
}
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
124
|
+
const extracted = extractLeadingTime(rest.trim());
|
|
125
|
+
const time = extracted?.time ?? defaultTime;
|
|
126
|
+
const message = (extracted ? extracted.message : rest).trim();
|
|
127
|
+
if (!time || !message)
|
|
160
128
|
return null;
|
|
129
|
+
let target = weekday
|
|
130
|
+
? nextWeekday(weekday, baseDate, time)
|
|
131
|
+
: applyTime(baseDate, time.hours, time.minutes);
|
|
132
|
+
if (!target)
|
|
133
|
+
return null;
|
|
134
|
+
if (allowRollForward && target.getTime() <= now.getTime()) {
|
|
135
|
+
target = new Date(target);
|
|
136
|
+
target.setDate(target.getDate() + 1);
|
|
161
137
|
}
|
|
162
|
-
|
|
138
|
+
// A pinned future day (tomorrow / in N days / next <weekday>) that still
|
|
139
|
+
// lands in the past means the input was contradictory.
|
|
140
|
+
if (target.getTime() <= now.getTime())
|
|
141
|
+
return null;
|
|
142
|
+
return { message, scheduledAt: target.getTime() };
|
|
163
143
|
}
|
|
164
144
|
//# sourceMappingURL=reminder-parser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reminder-parser.js","sourceRoot":"","sources":["../../src/reminder-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"reminder-parser.js","sourceRoot":"","sources":["../../src/reminder-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified task manager —
|
|
2
|
+
* Unified task manager — the single owner of the task store.
|
|
3
3
|
* Manages tasks across both personal (~/.curie-agent/tasks.json) and project (<cwd>/tasks.json) scopes.
|
|
4
|
+
*
|
|
5
|
+
* Two invariants matter here:
|
|
6
|
+
* 1. Nothing is ever deleted automatically. Only explicit user actions
|
|
7
|
+
* (`removeTask`, `clearCompleted`) drop a task.
|
|
8
|
+
* 2. Every write goes through `mutate()`, which re-reads the file if another
|
|
9
|
+
* process touched it since our last read. Without this, two in-memory
|
|
10
|
+
* copies of the task array silently overwrite each other's work.
|
|
4
11
|
*/
|
|
5
12
|
import type { ScheduleType, TaskMode, TaskPriority, TaskScope, TaskStatus, UnifiedTask, TasksFile } from './unified-task.js';
|
|
6
13
|
export declare class TaskManager {
|
|
7
14
|
private data;
|
|
8
|
-
/** TTL for pruning completed tasks. */
|
|
9
|
-
private ttlMs;
|
|
10
15
|
/** File path for persistence. */
|
|
11
16
|
private filePath;
|
|
17
|
+
/** Fingerprint of the file as of our last read/write — used to detect foreign writes. */
|
|
18
|
+
private seenFingerprint;
|
|
12
19
|
/** Set of task IDs currently being executed (prevents re-firing). */
|
|
13
20
|
private executing;
|
|
14
|
-
constructor(
|
|
21
|
+
constructor(filePath?: string);
|
|
15
22
|
/** Reload the file from disk. */
|
|
16
23
|
load(): TasksFile;
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Persist the in-memory array as-is. Prefer the mutating methods below —
|
|
26
|
+
* they reload first, so they can't clobber another writer's changes.
|
|
27
|
+
*/
|
|
18
28
|
save(): void;
|
|
29
|
+
/** Re-read the file if someone else wrote it since we last looked. */
|
|
30
|
+
private refreshIfStale;
|
|
31
|
+
/**
|
|
32
|
+
* The only write path: reload-if-stale → mutate → persist.
|
|
33
|
+
* All lookups must happen inside `fn`, since a refresh replaces the array.
|
|
34
|
+
*/
|
|
35
|
+
private mutate;
|
|
19
36
|
/** Create a new task with the given mode and scope. */
|
|
20
37
|
create(options: {
|
|
21
38
|
title: string;
|
|
@@ -35,7 +52,16 @@ export declare class TaskManager {
|
|
|
35
52
|
updateTaskStatus(id: string, status: TaskStatus): boolean;
|
|
36
53
|
/** Advance through the manual task lifecycle (todo → in_progress → done). */
|
|
37
54
|
setTaskStatus(id: string, status: 'todo' | 'in_progress'): boolean;
|
|
38
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Apply a partial patch to a task. Use this instead of mutating the object
|
|
57
|
+
* returned by `findTask()` and calling `save()` — that pattern races.
|
|
58
|
+
*/
|
|
59
|
+
updateTask(id: string, patch: Partial<Omit<UnifiedTask, 'id'>>): UnifiedTask | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Find a single task by ID. Falls back to a unique prefix match, so the
|
|
62
|
+
* truncated IDs shown by `/todo list` and `/cron list` are usable directly.
|
|
63
|
+
* Ambiguous prefixes resolve to undefined rather than an arbitrary task.
|
|
64
|
+
*/
|
|
39
65
|
findTask(id: string): UnifiedTask | undefined;
|
|
40
66
|
/** List tasks with optional filters. */
|
|
41
67
|
list(options?: {
|
|
@@ -44,7 +70,7 @@ export declare class TaskManager {
|
|
|
44
70
|
scope?: TaskScope;
|
|
45
71
|
priority?: TaskPriority;
|
|
46
72
|
}): UnifiedTask[];
|
|
47
|
-
/** Get pending scheduled tasks that are due (mode=
|
|
73
|
+
/** Get pending scheduled tasks that are due (mode=agent or notify with scheduled_at <= now). */
|
|
48
74
|
getNextTasks(now?: number): UnifiedTask[];
|
|
49
75
|
/** Mark a task as currently executing. */
|
|
50
76
|
markExecuting(id: string): boolean;
|
|
@@ -52,23 +78,30 @@ export declare class TaskManager {
|
|
|
52
78
|
clearExecuting(id: string): void;
|
|
53
79
|
/** Cancel (soft-delete) a pending/fired task. */
|
|
54
80
|
cancelTask(id: string): boolean;
|
|
55
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Remove completed/canceled/failed tasks (hard-delete).
|
|
83
|
+
* Explicit user action only — nothing calls this on a timer.
|
|
84
|
+
*/
|
|
56
85
|
clearCompleted(): number;
|
|
57
86
|
/** Remove a task permanently. */
|
|
58
87
|
removeTask(id: string): boolean;
|
|
88
|
+
/** Reorder tasks to match the given ID sequence. IDs not listed keep their relative order. */
|
|
89
|
+
reorder(ids: string[]): number;
|
|
59
90
|
/** Renormalize the `order` field for all tasks in scope. */
|
|
60
91
|
private renormalizeOrders;
|
|
61
|
-
/** Remove completed tasks older than the cutoff. Pending/pending-like tasks always kept. */
|
|
62
|
-
pruneOld(cutoff: number): number;
|
|
63
92
|
/** Count of pending tasks. */
|
|
64
93
|
get pendingCount(): number;
|
|
65
|
-
/** Check if a task is a recurring heartbeat (
|
|
94
|
+
/** Check if a task is a recurring heartbeat (agent mode with a frequency). */
|
|
66
95
|
isHeartbeat(task: UnifiedTask): boolean;
|
|
67
96
|
/** Get all pending heartbeat tasks. */
|
|
68
97
|
getHeartbeats(): UnifiedTask[];
|
|
69
98
|
/**
|
|
70
99
|
* Evaluate all five heartbeat schedule settings and ensure exactly one
|
|
71
|
-
* pending
|
|
100
|
+
* pending agent+frequency task exists in the store. Called after settings change.
|
|
101
|
+
*
|
|
102
|
+
* If the winning schedule is unchanged, `scheduled_at` is left alone —
|
|
103
|
+
* recomputing it from "now" on every config change silently skips the slot
|
|
104
|
+
* the user was waiting for.
|
|
72
105
|
*/
|
|
73
106
|
rescheduleFromSettings(settings: {
|
|
74
107
|
HEARTBEAT_INTRADAY?: string;
|
|
@@ -80,4 +113,12 @@ export declare class TaskManager {
|
|
|
80
113
|
/** Cancel all pending heartbeat tasks. Returns count cancelled. */
|
|
81
114
|
cancelAllHeartbeats(): number;
|
|
82
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Get the process-shared TaskManager for a store path. Every writer in a
|
|
118
|
+
* process must go through this — separate instances hold separate copies of
|
|
119
|
+
* the task array and overwrite each other on save.
|
|
120
|
+
*/
|
|
121
|
+
export declare function getTaskManager(filePath?: string): TaskManager;
|
|
122
|
+
/** Drop all memoized instances. Test helper. */
|
|
123
|
+
export declare function resetTaskManagers(): void;
|
|
83
124
|
//# sourceMappingURL=task-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../../src/task-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../../src/task-manager.ts"],"names":[],"mappings":"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"}
|