@siftd/connect-agent 0.2.38 → 0.2.39
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/core/task-queue.d.ts +125 -0
- package/dist/core/task-queue.js +248 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/orchestrator.d.ts +62 -0
- package/dist/orchestrator.js +212 -0
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
export type TaskPriority = 'urgent' | 'high' | 'normal' | 'low';
|
|
9
|
+
export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
|
|
10
|
+
export interface LiaTask {
|
|
11
|
+
id: string;
|
|
12
|
+
type: 'user_message' | 'follow_up' | 'scheduled' | 'background';
|
|
13
|
+
content: string;
|
|
14
|
+
priority: TaskPriority;
|
|
15
|
+
status: TaskStatus;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
startedAt?: Date;
|
|
18
|
+
completedAt?: Date;
|
|
19
|
+
result?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
metadata?: {
|
|
22
|
+
userId?: string;
|
|
23
|
+
messageId?: string;
|
|
24
|
+
parentTaskId?: string;
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
context?: 'personal' | 'team';
|
|
27
|
+
orgId?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface LiaPlan {
|
|
31
|
+
id: string;
|
|
32
|
+
goal: string;
|
|
33
|
+
steps: LiaPlanStep[];
|
|
34
|
+
status: 'planning' | 'executing' | 'completed' | 'failed';
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
completedAt?: Date;
|
|
37
|
+
}
|
|
38
|
+
export interface LiaPlanStep {
|
|
39
|
+
id: string;
|
|
40
|
+
description: string;
|
|
41
|
+
status: TaskStatus;
|
|
42
|
+
taskId?: string;
|
|
43
|
+
notes?: string;
|
|
44
|
+
}
|
|
45
|
+
export type TaskQueueCallback = (task: LiaTask) => void;
|
|
46
|
+
export type PlanCallback = (plan: LiaPlan) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Lia's task queue - manages her internal todo list
|
|
49
|
+
*/
|
|
50
|
+
export declare class LiaTaskQueue {
|
|
51
|
+
private tasks;
|
|
52
|
+
private plans;
|
|
53
|
+
private queue;
|
|
54
|
+
private currentTask;
|
|
55
|
+
private isProcessing;
|
|
56
|
+
private onTaskUpdate?;
|
|
57
|
+
private onPlanUpdate?;
|
|
58
|
+
private processTask?;
|
|
59
|
+
constructor(options?: {
|
|
60
|
+
onTaskUpdate?: TaskQueueCallback;
|
|
61
|
+
onPlanUpdate?: PlanCallback;
|
|
62
|
+
processTask?: (task: LiaTask) => Promise<string>;
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Add a new task to the queue
|
|
66
|
+
*/
|
|
67
|
+
addTask(task: Omit<LiaTask, 'id' | 'status' | 'createdAt'>): LiaTask;
|
|
68
|
+
/**
|
|
69
|
+
* Insert task ID into queue based on priority
|
|
70
|
+
*/
|
|
71
|
+
private insertByPriority;
|
|
72
|
+
/**
|
|
73
|
+
* Start processing the queue
|
|
74
|
+
*/
|
|
75
|
+
startProcessing(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the current task being processed
|
|
78
|
+
*/
|
|
79
|
+
getCurrentTask(): LiaTask | null;
|
|
80
|
+
/**
|
|
81
|
+
* Get all pending tasks
|
|
82
|
+
*/
|
|
83
|
+
getPendingTasks(): LiaTask[];
|
|
84
|
+
/**
|
|
85
|
+
* Get recent tasks (last N)
|
|
86
|
+
*/
|
|
87
|
+
getRecentTasks(limit?: number): LiaTask[];
|
|
88
|
+
/**
|
|
89
|
+
* Get task by ID
|
|
90
|
+
*/
|
|
91
|
+
getTask(taskId: string): LiaTask | null;
|
|
92
|
+
/**
|
|
93
|
+
* Cancel a pending task
|
|
94
|
+
*/
|
|
95
|
+
cancelTask(taskId: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Create a plan (breaks down a goal into steps)
|
|
98
|
+
*/
|
|
99
|
+
createPlan(goal: string, steps: string[]): LiaPlan;
|
|
100
|
+
/**
|
|
101
|
+
* Update a plan step
|
|
102
|
+
*/
|
|
103
|
+
updatePlanStep(planId: string, stepId: string, update: Partial<LiaPlanStep>): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Get current plan
|
|
106
|
+
*/
|
|
107
|
+
getCurrentPlan(): LiaPlan | null;
|
|
108
|
+
/**
|
|
109
|
+
* Get all plans
|
|
110
|
+
*/
|
|
111
|
+
getPlans(): LiaPlan[];
|
|
112
|
+
/**
|
|
113
|
+
* Get queue status for display
|
|
114
|
+
*/
|
|
115
|
+
getStatus(): {
|
|
116
|
+
isProcessing: boolean;
|
|
117
|
+
currentTask: LiaTask | null;
|
|
118
|
+
pendingCount: number;
|
|
119
|
+
queuePreview: string[];
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Format tasks as readable todo list (for LLM context)
|
|
123
|
+
*/
|
|
124
|
+
formatAsTodoList(): string;
|
|
125
|
+
}
|
|
@@ -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
package/dist/index.js
CHANGED
package/dist/orchestrator.d.ts
CHANGED
|
@@ -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;
|
|
@@ -67,6 +68,9 @@ export declare class MasterOrchestrator {
|
|
|
67
68
|
private calendarTools;
|
|
68
69
|
private sharedState;
|
|
69
70
|
private verboseMode;
|
|
71
|
+
private taskQueue;
|
|
72
|
+
private taskUpdateCallback?;
|
|
73
|
+
private planUpdateCallback?;
|
|
70
74
|
constructor(options: {
|
|
71
75
|
apiKey: string;
|
|
72
76
|
userId: string;
|
|
@@ -95,6 +99,52 @@ export declare class MasterOrchestrator {
|
|
|
95
99
|
* Set callback for gallery updates (worker assets for UI gallery view)
|
|
96
100
|
*/
|
|
97
101
|
setGalleryCallback(callback: GalleryCallback | null): void;
|
|
102
|
+
/**
|
|
103
|
+
* Set callback for task queue updates (for real-time UI updates)
|
|
104
|
+
*/
|
|
105
|
+
setTaskUpdateCallback(callback: ((task: LiaTask) => void) | null): void;
|
|
106
|
+
/**
|
|
107
|
+
* Set callback for plan updates (for real-time UI updates)
|
|
108
|
+
*/
|
|
109
|
+
setPlanUpdateCallback(callback: ((plan: LiaPlan) => void) | null): void;
|
|
110
|
+
/**
|
|
111
|
+
* Queue a message for async processing (non-blocking)
|
|
112
|
+
* Returns immediately with task ID - use callbacks or getTaskQueueStatus for results
|
|
113
|
+
*/
|
|
114
|
+
queueMessage(message: string, options?: {
|
|
115
|
+
priority?: 'urgent' | 'high' | 'normal' | 'low';
|
|
116
|
+
apiKey?: string;
|
|
117
|
+
messageId?: string;
|
|
118
|
+
}): LiaTask;
|
|
119
|
+
/**
|
|
120
|
+
* Process a task from the queue (called by LiaTaskQueue)
|
|
121
|
+
*/
|
|
122
|
+
private processQueuedTask;
|
|
123
|
+
/**
|
|
124
|
+
* Get task queue status (for UI)
|
|
125
|
+
*/
|
|
126
|
+
getTaskQueueStatus(): {
|
|
127
|
+
isProcessing: boolean;
|
|
128
|
+
currentTask: LiaTask | null;
|
|
129
|
+
pendingCount: number;
|
|
130
|
+
queuePreview: string[];
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Get pending tasks
|
|
134
|
+
*/
|
|
135
|
+
getPendingTasks(): LiaTask[];
|
|
136
|
+
/**
|
|
137
|
+
* Get task by ID
|
|
138
|
+
*/
|
|
139
|
+
getTask(taskId: string): LiaTask | null;
|
|
140
|
+
/**
|
|
141
|
+
* Cancel a pending task
|
|
142
|
+
*/
|
|
143
|
+
cancelTask(taskId: string): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Get Lia's internal todo list formatted for context
|
|
146
|
+
*/
|
|
147
|
+
getTaskQueueContext(): string;
|
|
98
148
|
/**
|
|
99
149
|
* Get gallery workers with their assets
|
|
100
150
|
*/
|
|
@@ -238,6 +288,18 @@ export declare class MasterOrchestrator {
|
|
|
238
288
|
* Stop a running server on a port
|
|
239
289
|
*/
|
|
240
290
|
private executeStopServer;
|
|
291
|
+
/**
|
|
292
|
+
* Create a plan with steps (Lia's internal planning)
|
|
293
|
+
*/
|
|
294
|
+
private executeLiaPlan;
|
|
295
|
+
/**
|
|
296
|
+
* Add a task to Lia's internal queue
|
|
297
|
+
*/
|
|
298
|
+
private executeLiaAddTask;
|
|
299
|
+
/**
|
|
300
|
+
* Get Lia's task queue status
|
|
301
|
+
*/
|
|
302
|
+
private executeLiaGetQueue;
|
|
241
303
|
/**
|
|
242
304
|
* Format tool preview for user
|
|
243
305
|
*/
|
package/dist/orchestrator.js
CHANGED
|
@@ -20,6 +20,7 @@ import { SharedState } from './workers/shared-state.js';
|
|
|
20
20
|
import { getKnowledgeForPrompt } from './genesis/index.js';
|
|
21
21
|
import { loadHubContext, formatHubContext, logAction, logWorker } from './core/hub.js';
|
|
22
22
|
import { buildWorkerPrompt } from './prompts/worker-system.js';
|
|
23
|
+
import { LiaTaskQueue } from './core/task-queue.js';
|
|
23
24
|
/**
|
|
24
25
|
* Extract file paths from worker output
|
|
25
26
|
* Workers naturally mention files they create: "Created /tmp/foo.html", "Saved to /path/file"
|
|
@@ -74,6 +75,14 @@ YOUR IDENTITY:
|
|
|
74
75
|
HOW YOU WORK:
|
|
75
76
|
You are the orchestrator, not the executor. You coordinate and remember while workers do the hands-on work.
|
|
76
77
|
|
|
78
|
+
TASK QUEUE (Always-Listening):
|
|
79
|
+
You have an internal task queue. Users can keep sending messages while you work - you're interruptible.
|
|
80
|
+
- Use lia_plan to break down complex goals into steps
|
|
81
|
+
- Use lia_add_task to queue follow-up work or new requests that come in
|
|
82
|
+
- Use lia_get_queue to see what's pending
|
|
83
|
+
- When you receive multiple requests, acknowledge them and queue them: "Got it, adding to my queue..."
|
|
84
|
+
- You can work on one task while others wait - always stay responsive
|
|
85
|
+
|
|
77
86
|
TOOL RULES:
|
|
78
87
|
|
|
79
88
|
✅ bash - READ-ONLY operations only:
|
|
@@ -179,6 +188,10 @@ export class MasterOrchestrator {
|
|
|
179
188
|
calendarTools;
|
|
180
189
|
sharedState;
|
|
181
190
|
verboseMode = new Map(); // per-user verbose mode
|
|
191
|
+
// Lia's internal task queue - allows async message processing
|
|
192
|
+
taskQueue;
|
|
193
|
+
taskUpdateCallback;
|
|
194
|
+
planUpdateCallback;
|
|
182
195
|
constructor(options) {
|
|
183
196
|
this.client = new Anthropic({ apiKey: options.apiKey });
|
|
184
197
|
this.model = options.model || 'claude-sonnet-4-20250514';
|
|
@@ -261,6 +274,12 @@ export class MasterOrchestrator {
|
|
|
261
274
|
this.workerLogCallback(workerId, line, stream);
|
|
262
275
|
}
|
|
263
276
|
});
|
|
277
|
+
// Initialize Lia's internal task queue
|
|
278
|
+
this.taskQueue = new LiaTaskQueue({
|
|
279
|
+
onTaskUpdate: (task) => this.taskUpdateCallback?.(task),
|
|
280
|
+
onPlanUpdate: (plan) => this.planUpdateCallback?.(plan),
|
|
281
|
+
processTask: async (task) => this.processQueuedTask(task),
|
|
282
|
+
});
|
|
264
283
|
}
|
|
265
284
|
/**
|
|
266
285
|
* Initialize the orchestrator - indexes filesystem on first call
|
|
@@ -318,6 +337,75 @@ export class MasterOrchestrator {
|
|
|
318
337
|
this.broadcastGalleryUpdate();
|
|
319
338
|
} : null);
|
|
320
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Set callback for task queue updates (for real-time UI updates)
|
|
342
|
+
*/
|
|
343
|
+
setTaskUpdateCallback(callback) {
|
|
344
|
+
this.taskUpdateCallback = callback || undefined;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Set callback for plan updates (for real-time UI updates)
|
|
348
|
+
*/
|
|
349
|
+
setPlanUpdateCallback(callback) {
|
|
350
|
+
this.planUpdateCallback = callback || undefined;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Queue a message for async processing (non-blocking)
|
|
354
|
+
* Returns immediately with task ID - use callbacks or getTaskQueueStatus for results
|
|
355
|
+
*/
|
|
356
|
+
queueMessage(message, options) {
|
|
357
|
+
return this.taskQueue.addTask({
|
|
358
|
+
type: 'user_message',
|
|
359
|
+
content: message,
|
|
360
|
+
priority: options?.priority || 'normal',
|
|
361
|
+
metadata: {
|
|
362
|
+
userId: this.userId,
|
|
363
|
+
messageId: options?.messageId,
|
|
364
|
+
apiKey: options?.apiKey,
|
|
365
|
+
context: this.instanceMode,
|
|
366
|
+
orgId: this.orgId,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Process a task from the queue (called by LiaTaskQueue)
|
|
372
|
+
*/
|
|
373
|
+
async processQueuedTask(task) {
|
|
374
|
+
// This is where the actual work happens
|
|
375
|
+
return this.processMessage(task.content, [], // Fresh conversation for each task
|
|
376
|
+
undefined, // No sendMessage callback for queued tasks
|
|
377
|
+
task.metadata?.apiKey);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get task queue status (for UI)
|
|
381
|
+
*/
|
|
382
|
+
getTaskQueueStatus() {
|
|
383
|
+
return this.taskQueue.getStatus();
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get pending tasks
|
|
387
|
+
*/
|
|
388
|
+
getPendingTasks() {
|
|
389
|
+
return this.taskQueue.getPendingTasks();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get task by ID
|
|
393
|
+
*/
|
|
394
|
+
getTask(taskId) {
|
|
395
|
+
return this.taskQueue.getTask(taskId);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Cancel a pending task
|
|
399
|
+
*/
|
|
400
|
+
cancelTask(taskId) {
|
|
401
|
+
return this.taskQueue.cancelTask(taskId);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get Lia's internal todo list formatted for context
|
|
405
|
+
*/
|
|
406
|
+
getTaskQueueContext() {
|
|
407
|
+
return this.taskQueue.formatAsTodoList();
|
|
408
|
+
}
|
|
321
409
|
/**
|
|
322
410
|
* Get gallery workers with their assets
|
|
323
411
|
*/
|
|
@@ -1540,6 +1628,54 @@ Be specific about what you want done.`,
|
|
|
1540
1628
|
},
|
|
1541
1629
|
required: ['port']
|
|
1542
1630
|
}
|
|
1631
|
+
},
|
|
1632
|
+
// Lia's internal task management tools
|
|
1633
|
+
{
|
|
1634
|
+
name: 'lia_plan',
|
|
1635
|
+
description: `Create a plan to break down a complex goal into steps. Use this when you receive multiple requests or need to organize work. The plan becomes your internal todo list.`,
|
|
1636
|
+
input_schema: {
|
|
1637
|
+
type: 'object',
|
|
1638
|
+
properties: {
|
|
1639
|
+
goal: {
|
|
1640
|
+
type: 'string',
|
|
1641
|
+
description: 'The overall goal you are trying to accomplish'
|
|
1642
|
+
},
|
|
1643
|
+
steps: {
|
|
1644
|
+
type: 'array',
|
|
1645
|
+
items: { type: 'string' },
|
|
1646
|
+
description: 'List of steps to accomplish the goal'
|
|
1647
|
+
}
|
|
1648
|
+
},
|
|
1649
|
+
required: ['goal', 'steps']
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
name: 'lia_add_task',
|
|
1654
|
+
description: `Add a task to your internal queue. Use when the user sends a new request while you are busy, or when you identify follow-up work. Tasks are processed in priority order.`,
|
|
1655
|
+
input_schema: {
|
|
1656
|
+
type: 'object',
|
|
1657
|
+
properties: {
|
|
1658
|
+
task: {
|
|
1659
|
+
type: 'string',
|
|
1660
|
+
description: 'The task description'
|
|
1661
|
+
},
|
|
1662
|
+
priority: {
|
|
1663
|
+
type: 'string',
|
|
1664
|
+
enum: ['urgent', 'high', 'normal', 'low'],
|
|
1665
|
+
description: 'Task priority (default: normal)'
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
required: ['task']
|
|
1669
|
+
}
|
|
1670
|
+
},
|
|
1671
|
+
{
|
|
1672
|
+
name: 'lia_get_queue',
|
|
1673
|
+
description: 'Get your current task queue status. Shows what you are working on and what is pending.',
|
|
1674
|
+
input_schema: {
|
|
1675
|
+
type: 'object',
|
|
1676
|
+
properties: {},
|
|
1677
|
+
required: []
|
|
1678
|
+
}
|
|
1543
1679
|
}
|
|
1544
1680
|
];
|
|
1545
1681
|
}
|
|
@@ -1683,6 +1819,16 @@ Be specific about what you want done.`,
|
|
|
1683
1819
|
case 'stop_local_server':
|
|
1684
1820
|
result = await this.executeStopServer(input.port);
|
|
1685
1821
|
break;
|
|
1822
|
+
// Lia's internal task management tools
|
|
1823
|
+
case 'lia_plan':
|
|
1824
|
+
result = this.executeLiaPlan(input.goal, input.steps);
|
|
1825
|
+
break;
|
|
1826
|
+
case 'lia_add_task':
|
|
1827
|
+
result = this.executeLiaAddTask(input.task, input.priority);
|
|
1828
|
+
break;
|
|
1829
|
+
case 'lia_get_queue':
|
|
1830
|
+
result = this.executeLiaGetQueue();
|
|
1831
|
+
break;
|
|
1686
1832
|
default:
|
|
1687
1833
|
result = { success: false, output: `Unknown tool: ${toolUse.name}` };
|
|
1688
1834
|
}
|
|
@@ -2186,6 +2332,66 @@ Be specific about what you want done.`,
|
|
|
2186
2332
|
return { success: false, output: `No server running on port ${port}` };
|
|
2187
2333
|
}
|
|
2188
2334
|
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Create a plan with steps (Lia's internal planning)
|
|
2337
|
+
*/
|
|
2338
|
+
executeLiaPlan(goal, steps) {
|
|
2339
|
+
try {
|
|
2340
|
+
const plan = this.taskQueue.createPlan(goal, steps);
|
|
2341
|
+
console.log(`[ORCHESTRATOR] Created plan: ${plan.id} with ${steps.length} steps`);
|
|
2342
|
+
const stepsFormatted = steps.map((s, i) => `${i + 1}. ${s}`).join('\n');
|
|
2343
|
+
return {
|
|
2344
|
+
success: true,
|
|
2345
|
+
output: `Created plan "${goal}" with ${steps.length} steps:\n${stepsFormatted}\n\nPlan ID: ${plan.id}`
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
return { success: false, output: `Failed to create plan: ${error}` };
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Add a task to Lia's internal queue
|
|
2354
|
+
*/
|
|
2355
|
+
executeLiaAddTask(task, priority) {
|
|
2356
|
+
try {
|
|
2357
|
+
const newTask = this.taskQueue.addTask({
|
|
2358
|
+
type: 'follow_up',
|
|
2359
|
+
content: task,
|
|
2360
|
+
priority: priority || 'normal',
|
|
2361
|
+
metadata: {
|
|
2362
|
+
userId: this.userId,
|
|
2363
|
+
context: this.instanceMode,
|
|
2364
|
+
orgId: this.orgId,
|
|
2365
|
+
},
|
|
2366
|
+
});
|
|
2367
|
+
console.log(`[ORCHESTRATOR] Added task to queue: ${newTask.id}`);
|
|
2368
|
+
const status = this.taskQueue.getStatus();
|
|
2369
|
+
return {
|
|
2370
|
+
success: true,
|
|
2371
|
+
output: `Added to my queue: "${task}" (priority: ${priority || 'normal'}). ${status.pendingCount} tasks pending.`
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
catch (error) {
|
|
2375
|
+
return { success: false, output: `Failed to add task: ${error}` };
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Get Lia's task queue status
|
|
2380
|
+
*/
|
|
2381
|
+
executeLiaGetQueue() {
|
|
2382
|
+
try {
|
|
2383
|
+
const status = this.taskQueue.getStatus();
|
|
2384
|
+
const todoList = this.taskQueue.formatAsTodoList();
|
|
2385
|
+
let output = todoList;
|
|
2386
|
+
if (!status.isProcessing && status.pendingCount === 0) {
|
|
2387
|
+
output = 'My queue is empty - ready for new tasks.';
|
|
2388
|
+
}
|
|
2389
|
+
return { success: true, output };
|
|
2390
|
+
}
|
|
2391
|
+
catch (error) {
|
|
2392
|
+
return { success: false, output: `Failed to get queue: ${error}` };
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2189
2395
|
/**
|
|
2190
2396
|
* Format tool preview for user
|
|
2191
2397
|
*/
|
|
@@ -2235,6 +2441,12 @@ Be specific about what you want done.`,
|
|
|
2235
2441
|
return `Starting server on port ${input.port || 8080}...`;
|
|
2236
2442
|
case 'stop_local_server':
|
|
2237
2443
|
return `Stopping server on port ${input.port}...`;
|
|
2444
|
+
case 'lia_plan':
|
|
2445
|
+
return `Planning: ${input.goal}`;
|
|
2446
|
+
case 'lia_add_task':
|
|
2447
|
+
return `Queuing task: ${input.task.slice(0, 50)}...`;
|
|
2448
|
+
case 'lia_get_queue':
|
|
2449
|
+
return 'Checking my task queue...';
|
|
2238
2450
|
default:
|
|
2239
2451
|
return null;
|
|
2240
2452
|
}
|