@siftd/connect-agent 0.2.38 → 0.2.40

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.
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Lia's Internal Task Queue
3
+ *
4
+ * This manages Lia's own todo list - separate from user /todos.
5
+ * Allows async message processing where users can keep sending
6
+ * while Lia works through her queue.
7
+ */
8
+ /**
9
+ * Lia's task queue - manages her internal todo list
10
+ */
11
+ export class LiaTaskQueue {
12
+ tasks = new Map();
13
+ plans = new Map();
14
+ queue = []; // Task IDs in priority order
15
+ currentTask = null;
16
+ isProcessing = false;
17
+ // Callbacks for real-time updates
18
+ onTaskUpdate;
19
+ onPlanUpdate;
20
+ processTask;
21
+ constructor(options) {
22
+ this.onTaskUpdate = options?.onTaskUpdate;
23
+ this.onPlanUpdate = options?.onPlanUpdate;
24
+ this.processTask = options?.processTask;
25
+ }
26
+ /**
27
+ * Add a new task to the queue
28
+ */
29
+ addTask(task) {
30
+ const newTask = {
31
+ ...task,
32
+ id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
33
+ status: 'pending',
34
+ createdAt: new Date(),
35
+ };
36
+ this.tasks.set(newTask.id, newTask);
37
+ this.insertByPriority(newTask.id, newTask.priority);
38
+ this.onTaskUpdate?.(newTask);
39
+ // Auto-start processing if not already running
40
+ if (!this.isProcessing) {
41
+ this.startProcessing();
42
+ }
43
+ return newTask;
44
+ }
45
+ /**
46
+ * Insert task ID into queue based on priority
47
+ */
48
+ insertByPriority(taskId, priority) {
49
+ const priorityOrder = {
50
+ urgent: 0,
51
+ high: 1,
52
+ normal: 2,
53
+ low: 3,
54
+ };
55
+ const taskPriority = priorityOrder[priority];
56
+ let insertIndex = this.queue.length;
57
+ for (let i = 0; i < this.queue.length; i++) {
58
+ const existingTask = this.tasks.get(this.queue[i]);
59
+ if (existingTask && priorityOrder[existingTask.priority] > taskPriority) {
60
+ insertIndex = i;
61
+ break;
62
+ }
63
+ }
64
+ this.queue.splice(insertIndex, 0, taskId);
65
+ }
66
+ /**
67
+ * Start processing the queue
68
+ */
69
+ async startProcessing() {
70
+ if (this.isProcessing)
71
+ return;
72
+ this.isProcessing = true;
73
+ while (this.queue.length > 0) {
74
+ const taskId = this.queue.shift();
75
+ if (!taskId)
76
+ continue;
77
+ const task = this.tasks.get(taskId);
78
+ if (!task || task.status === 'cancelled')
79
+ continue;
80
+ this.currentTask = taskId;
81
+ task.status = 'in_progress';
82
+ task.startedAt = new Date();
83
+ this.onTaskUpdate?.(task);
84
+ try {
85
+ if (this.processTask) {
86
+ const result = await this.processTask(task);
87
+ task.result = result;
88
+ task.status = 'completed';
89
+ }
90
+ else {
91
+ task.status = 'failed';
92
+ task.error = 'No task processor configured';
93
+ }
94
+ }
95
+ catch (error) {
96
+ task.status = 'failed';
97
+ task.error = error instanceof Error ? error.message : String(error);
98
+ }
99
+ task.completedAt = new Date();
100
+ this.currentTask = null;
101
+ this.onTaskUpdate?.(task);
102
+ }
103
+ this.isProcessing = false;
104
+ }
105
+ /**
106
+ * Get the current task being processed
107
+ */
108
+ getCurrentTask() {
109
+ return this.currentTask ? this.tasks.get(this.currentTask) || null : null;
110
+ }
111
+ /**
112
+ * Get all pending tasks
113
+ */
114
+ getPendingTasks() {
115
+ return this.queue
116
+ .map(id => this.tasks.get(id))
117
+ .filter((t) => t !== undefined && t.status === 'pending');
118
+ }
119
+ /**
120
+ * Get recent tasks (last N)
121
+ */
122
+ getRecentTasks(limit = 10) {
123
+ return Array.from(this.tasks.values())
124
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
125
+ .slice(0, limit);
126
+ }
127
+ /**
128
+ * Get task by ID
129
+ */
130
+ getTask(taskId) {
131
+ return this.tasks.get(taskId) || null;
132
+ }
133
+ /**
134
+ * Cancel a pending task
135
+ */
136
+ cancelTask(taskId) {
137
+ const task = this.tasks.get(taskId);
138
+ if (!task || task.status !== 'pending')
139
+ return false;
140
+ task.status = 'cancelled';
141
+ this.queue = this.queue.filter(id => id !== taskId);
142
+ this.onTaskUpdate?.(task);
143
+ return true;
144
+ }
145
+ /**
146
+ * Create a plan (breaks down a goal into steps)
147
+ */
148
+ createPlan(goal, steps) {
149
+ const plan = {
150
+ id: `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
151
+ goal,
152
+ steps: steps.map((desc, i) => ({
153
+ id: `step_${i + 1}`,
154
+ description: desc,
155
+ status: 'pending',
156
+ })),
157
+ status: 'planning',
158
+ createdAt: new Date(),
159
+ };
160
+ this.plans.set(plan.id, plan);
161
+ this.onPlanUpdate?.(plan);
162
+ return plan;
163
+ }
164
+ /**
165
+ * Update a plan step
166
+ */
167
+ updatePlanStep(planId, stepId, update) {
168
+ const plan = this.plans.get(planId);
169
+ if (!plan)
170
+ return false;
171
+ const step = plan.steps.find(s => s.id === stepId);
172
+ if (!step)
173
+ return false;
174
+ Object.assign(step, update);
175
+ // Update plan status based on steps
176
+ const allCompleted = plan.steps.every(s => s.status === 'completed');
177
+ const anyFailed = plan.steps.some(s => s.status === 'failed');
178
+ const anyInProgress = plan.steps.some(s => s.status === 'in_progress');
179
+ if (allCompleted) {
180
+ plan.status = 'completed';
181
+ plan.completedAt = new Date();
182
+ }
183
+ else if (anyFailed) {
184
+ plan.status = 'failed';
185
+ }
186
+ else if (anyInProgress) {
187
+ plan.status = 'executing';
188
+ }
189
+ this.onPlanUpdate?.(plan);
190
+ return true;
191
+ }
192
+ /**
193
+ * Get current plan
194
+ */
195
+ getCurrentPlan() {
196
+ return Array.from(this.plans.values())
197
+ .find(p => p.status === 'planning' || p.status === 'executing') || null;
198
+ }
199
+ /**
200
+ * Get all plans
201
+ */
202
+ getPlans() {
203
+ return Array.from(this.plans.values());
204
+ }
205
+ /**
206
+ * Get queue status for display
207
+ */
208
+ getStatus() {
209
+ const pending = this.getPendingTasks();
210
+ return {
211
+ isProcessing: this.isProcessing,
212
+ currentTask: this.getCurrentTask(),
213
+ pendingCount: pending.length,
214
+ queuePreview: pending.slice(0, 5).map(t => t.content.length > 50 ? t.content.slice(0, 50) + '...' : t.content),
215
+ };
216
+ }
217
+ /**
218
+ * Format tasks as readable todo list (for LLM context)
219
+ */
220
+ formatAsTodoList() {
221
+ const current = this.getCurrentTask();
222
+ const pending = this.getPendingTasks();
223
+ const recent = this.getRecentTasks(5).filter(t => t.status === 'completed' || t.status === 'failed');
224
+ const lines = ['## My Task Queue'];
225
+ if (current) {
226
+ lines.push('', '### Currently Working On:');
227
+ lines.push(`- [→] ${current.content}`);
228
+ }
229
+ if (pending.length > 0) {
230
+ lines.push('', '### Pending:');
231
+ for (const task of pending.slice(0, 10)) {
232
+ const priority = task.priority !== 'normal' ? ` [${task.priority}]` : '';
233
+ lines.push(`- [ ] ${task.content}${priority}`);
234
+ }
235
+ if (pending.length > 10) {
236
+ lines.push(`- ... and ${pending.length - 10} more`);
237
+ }
238
+ }
239
+ if (recent.length > 0) {
240
+ lines.push('', '### Recently Completed:');
241
+ for (const task of recent) {
242
+ const icon = task.status === 'completed' ? '✓' : '✗';
243
+ lines.push(`- [${icon}] ${task.content}`);
244
+ }
245
+ }
246
+ return lines.join('\n');
247
+ }
248
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './config.js';
2
2
  export * from './api.js';
3
3
  export * from './agent.js';
4
4
  export * from './websocket.js';
5
+ export * from './core/task-queue.js';
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export * from './config.js';
2
2
  export * from './api.js';
3
3
  export * from './agent.js';
4
4
  export * from './websocket.js';
5
+ export * from './core/task-queue.js';
@@ -5,6 +5,7 @@
5
5
  * It does NOT do the work itself. Claude Code CLI workers do the work.
6
6
  */
7
7
  import type { MessageParam } from '@anthropic-ai/sdk/resources/messages';
8
+ import { LiaTask, LiaPlan } from './core/task-queue.js';
8
9
  export type MessageSender = (message: string) => Promise<void>;
9
10
  export interface WorkerStatus {
10
11
  id: string;
@@ -16,6 +17,15 @@ export interface WorkerStatus {
16
17
  }
17
18
  export type WorkerStatusCallback = (workers: WorkerStatus[]) => void;
18
19
  export type WorkerLogCallback = (workerId: string, line: string, stream: 'stdout' | 'stderr') => void;
20
+ export interface LiaTodoItem {
21
+ id: string;
22
+ content: string;
23
+ activeForm: string;
24
+ status: 'pending' | 'in_progress' | 'completed';
25
+ workerId?: string;
26
+ progress?: number;
27
+ }
28
+ export type TodoUpdateCallback = (todos: LiaTodoItem[]) => void;
19
29
  export interface WorkerAsset {
20
30
  path: string;
21
31
  name: string;
@@ -67,6 +77,11 @@ export declare class MasterOrchestrator {
67
77
  private calendarTools;
68
78
  private sharedState;
69
79
  private verboseMode;
80
+ private taskQueue;
81
+ private taskUpdateCallback?;
82
+ private planUpdateCallback?;
83
+ private currentTodos;
84
+ private todoUpdateCallback?;
70
85
  constructor(options: {
71
86
  apiKey: string;
72
87
  userId: string;
@@ -95,6 +110,55 @@ export declare class MasterOrchestrator {
95
110
  * Set callback for gallery updates (worker assets for UI gallery view)
96
111
  */
97
112
  setGalleryCallback(callback: GalleryCallback | null): void;
113
+ /**
114
+ * Set callback for task queue updates (for real-time UI updates)
115
+ */
116
+ setTaskUpdateCallback(callback: ((task: LiaTask) => void) | null): void;
117
+ /**
118
+ * Set callback for plan updates (for real-time UI updates)
119
+ */
120
+ setPlanUpdateCallback(callback: ((plan: LiaPlan) => void) | null): void;
121
+ /**
122
+ * Set callback for visible todo list updates (displayed inline in chat)
123
+ */
124
+ setTodoUpdateCallback(callback: TodoUpdateCallback | null): void;
125
+ /**
126
+ * Queue a message for async processing (non-blocking)
127
+ * Returns immediately with task ID - use callbacks or getTaskQueueStatus for results
128
+ */
129
+ queueMessage(message: string, options?: {
130
+ priority?: 'urgent' | 'high' | 'normal' | 'low';
131
+ messageId?: string;
132
+ }): LiaTask;
133
+ /**
134
+ * Process a task from the queue (called by LiaTaskQueue)
135
+ */
136
+ private processQueuedTask;
137
+ /**
138
+ * Get task queue status (for UI)
139
+ */
140
+ getTaskQueueStatus(): {
141
+ isProcessing: boolean;
142
+ currentTask: LiaTask | null;
143
+ pendingCount: number;
144
+ queuePreview: string[];
145
+ };
146
+ /**
147
+ * Get pending tasks
148
+ */
149
+ getPendingTasks(): LiaTask[];
150
+ /**
151
+ * Get task by ID
152
+ */
153
+ getTask(taskId: string): LiaTask | null;
154
+ /**
155
+ * Cancel a pending task
156
+ */
157
+ cancelTask(taskId: string): boolean;
158
+ /**
159
+ * Get Lia's internal todo list formatted for context
160
+ */
161
+ getTaskQueueContext(): string;
98
162
  /**
99
163
  * Get gallery workers with their assets
100
164
  */
@@ -131,9 +195,8 @@ export declare class MasterOrchestrator {
131
195
  isVerbose(): boolean;
132
196
  /**
133
197
  * Process a user message
134
- * @param apiKey Optional per-request API key (overrides default)
135
198
  */
136
- processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender, apiKey?: string): Promise<string>;
199
+ processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender): Promise<string>;
137
200
  private tryHandleCalendarTodo;
138
201
  private extractTodoItems;
139
202
  private extractCalendarEvents;
@@ -161,7 +224,6 @@ export declare class MasterOrchestrator {
161
224
  private isIgnoredEntity;
162
225
  /**
163
226
  * Run the agentic loop
164
- * @param apiKey Optional per-request API key (creates temporary client)
165
227
  */
166
228
  private runAgentLoop;
167
229
  /**
@@ -238,6 +300,22 @@ export declare class MasterOrchestrator {
238
300
  * Stop a running server on a port
239
301
  */
240
302
  private executeStopServer;
303
+ /**
304
+ * Create a plan with steps (Lia's internal planning)
305
+ */
306
+ private executeLiaPlan;
307
+ /**
308
+ * Add a task to Lia's internal queue
309
+ */
310
+ private executeLiaAddTask;
311
+ /**
312
+ * Get Lia's task queue status
313
+ */
314
+ private executeLiaGetQueue;
315
+ /**
316
+ * Update visible todo list (displayed inline in chat)
317
+ */
318
+ private executeLiaTodoWrite;
241
319
  /**
242
320
  * Format tool preview for user
243
321
  */