@siftd/connect-agent 0.2.45 → 0.2.47
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/orchestrator.d.ts +7 -1
- package/dist/orchestrator.js +315 -142
- package/dist/tools/worker.d.ts +1 -0
- package/dist/tools/worker.js +3 -1
- package/dist/websocket.d.ts +4 -1
- package/dist/websocket.js +25 -0
- package/package.json +1 -1
package/dist/orchestrator.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare class MasterOrchestrator {
|
|
|
66
66
|
private workerLogCallback;
|
|
67
67
|
private workerStatusInterval;
|
|
68
68
|
private attachmentContext;
|
|
69
|
+
private lastUserMessage;
|
|
69
70
|
private galleryCallback;
|
|
70
71
|
private userId;
|
|
71
72
|
private workspaceDir;
|
|
@@ -193,7 +194,12 @@ export declare class MasterOrchestrator {
|
|
|
193
194
|
private stripTodoSnapshot;
|
|
194
195
|
private hasTodoMutation;
|
|
195
196
|
private hasCalendarMutation;
|
|
196
|
-
private
|
|
197
|
+
private getUserTagHint;
|
|
198
|
+
private getBreakdownTarget;
|
|
199
|
+
private ensureBreakdownOriginalDone;
|
|
200
|
+
private getLocalDateKey;
|
|
201
|
+
private getLocalTimeZone;
|
|
202
|
+
private getTodoCalSystemPrompt;
|
|
197
203
|
private getToolChoice;
|
|
198
204
|
private withAttachments;
|
|
199
205
|
private updateFileScope;
|
package/dist/orchestrator.js
CHANGED
|
@@ -157,7 +157,9 @@ CALENDAR + TODO (Lia-managed data):
|
|
|
157
157
|
- Keep these files hidden; refer users to /cal or /todo in responses
|
|
158
158
|
- If the user provides subtasks or a bullet list under one task, store them as subtasks (unless they explicitly ask to split into multiple items)
|
|
159
159
|
- Preserve explicit due dates, times, and priorities exactly as given (YYYY-MM-DD, HH:MM)
|
|
160
|
+
- Preserve task titles verbatim (including bracketed tags like [lia-test-...], quoted titles, and casing). Do not strip tags or rewrite titles.
|
|
160
161
|
- If the user explicitly requests /todo or /cal changes, you MUST call the corresponding tool in the same response
|
|
162
|
+
- Never delegate /todo or /cal changes to workers; use the tools directly in the same response
|
|
161
163
|
- If the user says "Add to /todo:" followed by a list of deadlines/details, create ONE todo with notes unless they explicitly ask to split/break down
|
|
162
164
|
- Schemas:
|
|
163
165
|
- calendar.json: { "version": 1, "calendars": [...], "events": [...] }
|
|
@@ -185,6 +187,16 @@ Before complex work: Check CLAUDE.md → Read LANDMARKS.md → Search memory
|
|
|
185
187
|
After completing work: Update LANDMARKS.md → Remember learnings
|
|
186
188
|
|
|
187
189
|
You orchestrate through workers. You remember through memory. You never do arbitrary file operations directly (calendar_upsert_events and todo_upsert_items are the safe exceptions).`;
|
|
190
|
+
const TODO_CAL_SYSTEM_PROMPT_BASE = `You are Lia. Your ONLY job is to update /todo and /cal using tools.
|
|
191
|
+
|
|
192
|
+
Rules:
|
|
193
|
+
- If the user requests /todo changes, call todo_upsert_items.
|
|
194
|
+
- If the user requests /cal or calendar changes, call calendar_upsert_events.
|
|
195
|
+
- If both are requested, call BOTH tools in the same response.
|
|
196
|
+
- Preserve exact titles, dates, times, notes, and any bracketed tags (e.g., [lia-test-...]).
|
|
197
|
+
- Do not spawn workers or call any other tools.
|
|
198
|
+
- Do not ask follow-up questions; infer reasonable defaults from the request.
|
|
199
|
+
- After tool calls, respond briefly confirming the updates.`;
|
|
188
200
|
export class MasterOrchestrator {
|
|
189
201
|
client;
|
|
190
202
|
model;
|
|
@@ -206,6 +218,7 @@ export class MasterOrchestrator {
|
|
|
206
218
|
workerLogCallback = null;
|
|
207
219
|
workerStatusInterval = null;
|
|
208
220
|
attachmentContext = null;
|
|
221
|
+
lastUserMessage = null;
|
|
209
222
|
galleryCallback = null;
|
|
210
223
|
userId;
|
|
211
224
|
workspaceDir;
|
|
@@ -688,8 +701,66 @@ export class MasterOrchestrator {
|
|
|
688
701
|
const query = /\b(what|show|list|open|view|see)\b/.test(lower);
|
|
689
702
|
return target && action && !query;
|
|
690
703
|
}
|
|
691
|
-
|
|
692
|
-
|
|
704
|
+
getUserTagHint(message) {
|
|
705
|
+
if (!message)
|
|
706
|
+
return null;
|
|
707
|
+
const base = this.stripTodoSnapshot(message);
|
|
708
|
+
const matches = base.match(/\[[^\]]{2,64}\]/g) || [];
|
|
709
|
+
const unique = Array.from(new Set(matches.map((tag) => tag.trim()))).filter(Boolean);
|
|
710
|
+
if (unique.length !== 1)
|
|
711
|
+
return null;
|
|
712
|
+
return unique[0];
|
|
713
|
+
}
|
|
714
|
+
getBreakdownTarget(message) {
|
|
715
|
+
if (!message)
|
|
716
|
+
return null;
|
|
717
|
+
const stripped = this.stripTodoSnapshot(message);
|
|
718
|
+
if (!/\b(break(?:\s|-)?down|split|parse)\b/i.test(stripped))
|
|
719
|
+
return null;
|
|
720
|
+
const quoted = stripped.match(/["“]([^"”]{3,160})["”]/);
|
|
721
|
+
if (quoted && quoted[1]) {
|
|
722
|
+
return quoted[1].trim();
|
|
723
|
+
}
|
|
724
|
+
const inline = stripped.match(/break(?:\s|-)?down\s+the\s+(.+?)\s+(?:item|todo|task)\b/i);
|
|
725
|
+
if (inline && inline[1]) {
|
|
726
|
+
return inline[1].trim();
|
|
727
|
+
}
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
ensureBreakdownOriginalDone(items, targetTitle) {
|
|
731
|
+
const normalizedTarget = targetTitle.trim().toLowerCase();
|
|
732
|
+
let found = false;
|
|
733
|
+
const nextItems = items.map((item) => {
|
|
734
|
+
if (item?.title && item.title.trim().toLowerCase() === normalizedTarget) {
|
|
735
|
+
found = true;
|
|
736
|
+
return { ...item, done: true };
|
|
737
|
+
}
|
|
738
|
+
return item;
|
|
739
|
+
});
|
|
740
|
+
if (!found) {
|
|
741
|
+
nextItems.unshift({ title: targetTitle, done: true });
|
|
742
|
+
}
|
|
743
|
+
return nextItems;
|
|
744
|
+
}
|
|
745
|
+
getLocalDateKey(date = new Date()) {
|
|
746
|
+
const year = date.getFullYear();
|
|
747
|
+
const month = `${date.getMonth() + 1}`.padStart(2, '0');
|
|
748
|
+
const day = `${date.getDate()}`.padStart(2, '0');
|
|
749
|
+
return `${year}-${month}-${day}`;
|
|
750
|
+
}
|
|
751
|
+
getLocalTimeZone() {
|
|
752
|
+
try {
|
|
753
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
getTodoCalSystemPrompt() {
|
|
760
|
+
const today = this.getLocalDateKey();
|
|
761
|
+
const timeZone = this.getLocalTimeZone();
|
|
762
|
+
const tzNote = timeZone ? ` (${timeZone})` : '';
|
|
763
|
+
return `${TODO_CAL_SYSTEM_PROMPT_BASE}\n\nDate context:\n- Today is ${today}${tzNote}.\n- Use this to resolve relative dates like "today", "tomorrow", or "next week".`;
|
|
693
764
|
}
|
|
694
765
|
getToolChoice(messages) {
|
|
695
766
|
const last = messages[messages.length - 1];
|
|
@@ -700,7 +771,7 @@ export class MasterOrchestrator {
|
|
|
700
771
|
if (wantsTodo && !wantsCal) {
|
|
701
772
|
return { type: 'tool', name: 'todo_upsert_items' };
|
|
702
773
|
}
|
|
703
|
-
if (wantsCal && !wantsTodo
|
|
774
|
+
if (wantsCal && !wantsTodo) {
|
|
704
775
|
return { type: 'tool', name: 'calendar_upsert_events' };
|
|
705
776
|
}
|
|
706
777
|
return undefined;
|
|
@@ -751,8 +822,9 @@ export class MasterOrchestrator {
|
|
|
751
822
|
const teamDir = this.getTeamFilesDir();
|
|
752
823
|
if (!teamDir)
|
|
753
824
|
return {};
|
|
825
|
+
const hasTeamDir = existsSync(teamDir);
|
|
754
826
|
return {
|
|
755
|
-
workingDir: this.forceTeamWorkingDir ? teamDir : undefined,
|
|
827
|
+
workingDir: this.forceTeamWorkingDir && hasTeamDir ? teamDir : undefined,
|
|
756
828
|
instructions: `TEAM FILES DIRECTORY: ${teamDir}\nSave any requested files under this directory so they appear in /files (Team Files). Create the directory if needed.`,
|
|
757
829
|
};
|
|
758
830
|
}
|
|
@@ -774,12 +846,28 @@ export class MasterOrchestrator {
|
|
|
774
846
|
* Process a user message
|
|
775
847
|
*/
|
|
776
848
|
async processMessage(message, conversationHistory = [], sendMessage) {
|
|
849
|
+
this.lastUserMessage = message;
|
|
777
850
|
// Handle slash commands first
|
|
778
851
|
const slashResponse = this.handleSlashCommand(message);
|
|
779
852
|
if (slashResponse) {
|
|
780
853
|
return slashResponse;
|
|
781
854
|
}
|
|
782
855
|
this.updateFileScope(message);
|
|
856
|
+
const wantsTodoOrCal = this.hasTodoMutation(message) || this.hasCalendarMutation(message);
|
|
857
|
+
if (wantsTodoOrCal) {
|
|
858
|
+
this.attachmentContext = this.extractAttachmentContext(message);
|
|
859
|
+
const toolMessages = [{ role: 'user', content: message }];
|
|
860
|
+
try {
|
|
861
|
+
return await this.runAgentLoop(toolMessages, this.getTodoCalSystemPrompt(), sendMessage);
|
|
862
|
+
}
|
|
863
|
+
catch (error) {
|
|
864
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
865
|
+
return `Error: ${errorMessage}`;
|
|
866
|
+
}
|
|
867
|
+
finally {
|
|
868
|
+
this.attachmentContext = null;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
783
871
|
// DISABLED: Dumb regex extraction was creating garbage todos
|
|
784
872
|
// Let the AI use calendar_upsert_events and todo_upsert_items tools properly
|
|
785
873
|
// const quickWrite = this.tryHandleCalendarTodo(message);
|
|
@@ -1230,14 +1318,34 @@ ${hubContextStr}
|
|
|
1230
1318
|
* Run the agentic loop
|
|
1231
1319
|
*/
|
|
1232
1320
|
async runAgentLoop(messages, system, sendMessage) {
|
|
1233
|
-
const
|
|
1321
|
+
const toolsBase = this.getToolDefinitions();
|
|
1322
|
+
const last = messages[messages.length - 1];
|
|
1323
|
+
const lastContent = last && last.role === 'user' && typeof last.content === 'string' ? last.content : '';
|
|
1324
|
+
const restrictWorkers = lastContent
|
|
1325
|
+
? (this.hasTodoMutation(lastContent) || this.hasCalendarMutation(lastContent))
|
|
1326
|
+
: false;
|
|
1327
|
+
const wantsTodoOrCal = restrictWorkers;
|
|
1328
|
+
const todoCalTools = new Set(['calendar_upsert_events', 'todo_upsert_items']);
|
|
1329
|
+
const blockedTools = new Set([
|
|
1330
|
+
'spawn_worker',
|
|
1331
|
+
'delegate_to_worker',
|
|
1332
|
+
'check_worker',
|
|
1333
|
+
'wait_worker',
|
|
1334
|
+
'list_workers',
|
|
1335
|
+
'cancel_worker',
|
|
1336
|
+
]);
|
|
1337
|
+
const tools = wantsTodoOrCal
|
|
1338
|
+
? toolsBase.filter((tool) => todoCalTools.has(tool.name))
|
|
1339
|
+
: toolsBase.filter((tool) => !blockedTools.has(tool.name) || !restrictWorkers);
|
|
1234
1340
|
let currentMessages = [...messages];
|
|
1235
1341
|
let iterations = 0;
|
|
1236
1342
|
const maxIterations = 30; // Increased for complex multi-tool tasks
|
|
1237
1343
|
const requestTimeoutMs = 60000;
|
|
1344
|
+
const forcedToolChoice = this.getToolChoice(currentMessages);
|
|
1345
|
+
let retriedForcedTool = false;
|
|
1238
1346
|
while (iterations < maxIterations) {
|
|
1239
1347
|
iterations++;
|
|
1240
|
-
const toolChoice = this.getToolChoice(currentMessages);
|
|
1348
|
+
const toolChoice = forcedToolChoice ?? this.getToolChoice(currentMessages);
|
|
1241
1349
|
const requestStart = Date.now();
|
|
1242
1350
|
console.log(`[ORCHESTRATOR] Anthropic request ${iterations}/${maxIterations} (model: ${this.model})`);
|
|
1243
1351
|
const response = await Promise.race([
|
|
@@ -1258,10 +1366,32 @@ ${hubContextStr}
|
|
|
1258
1366
|
console.log(`[ORCHESTRATOR] Anthropic response ${iterations}/${maxIterations} in ${Date.now() - requestStart}ms`);
|
|
1259
1367
|
// Check if done
|
|
1260
1368
|
if (response.stop_reason === 'end_turn' || !this.hasToolUse(response.content)) {
|
|
1369
|
+
if (forcedToolChoice && !retriedForcedTool) {
|
|
1370
|
+
retriedForcedTool = true;
|
|
1371
|
+
currentMessages = [
|
|
1372
|
+
...currentMessages,
|
|
1373
|
+
{ role: 'assistant', content: response.content },
|
|
1374
|
+
{
|
|
1375
|
+
role: 'user',
|
|
1376
|
+
content: `You must call the ${forcedToolChoice.name} tool now. Use the exact task/event titles and any bracketed tags exactly as provided. Do not spawn workers.`
|
|
1377
|
+
}
|
|
1378
|
+
];
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1261
1381
|
return this.extractText(response.content);
|
|
1262
1382
|
}
|
|
1383
|
+
const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
|
|
1384
|
+
const toolNames = toolUseBlocks.map((block) => block.name);
|
|
1263
1385
|
// Process tool calls
|
|
1264
1386
|
const toolResults = await this.processToolCalls(response.content, sendMessage);
|
|
1387
|
+
if (wantsTodoOrCal && (toolNames.includes('todo_upsert_items') || toolNames.includes('calendar_upsert_events'))) {
|
|
1388
|
+
const updates = [];
|
|
1389
|
+
if (toolNames.includes('todo_upsert_items'))
|
|
1390
|
+
updates.push('Updated /todo.');
|
|
1391
|
+
if (toolNames.includes('calendar_upsert_events'))
|
|
1392
|
+
updates.push('Updated /cal.');
|
|
1393
|
+
return `${updates.join(' ')} Open /todo or /cal to view.`;
|
|
1394
|
+
}
|
|
1265
1395
|
// Continue conversation
|
|
1266
1396
|
currentMessages = [
|
|
1267
1397
|
...currentMessages,
|
|
@@ -1877,6 +2007,17 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
1877
2007
|
async processToolCalls(content, sendMessage) {
|
|
1878
2008
|
const toolUseBlocks = content.filter((block) => block.type === 'tool_use');
|
|
1879
2009
|
const results = [];
|
|
2010
|
+
const wantsTodoOrCal = this.lastUserMessage
|
|
2011
|
+
? (this.hasTodoMutation(this.lastUserMessage) || this.hasCalendarMutation(this.lastUserMessage))
|
|
2012
|
+
: false;
|
|
2013
|
+
const blockedWorkerTools = new Set([
|
|
2014
|
+
'spawn_worker',
|
|
2015
|
+
'delegate_to_worker',
|
|
2016
|
+
'check_worker',
|
|
2017
|
+
'wait_worker',
|
|
2018
|
+
'list_workers',
|
|
2019
|
+
'cancel_worker',
|
|
2020
|
+
]);
|
|
1880
2021
|
for (const toolUse of toolUseBlocks) {
|
|
1881
2022
|
const input = toolUse.input;
|
|
1882
2023
|
let result;
|
|
@@ -1886,151 +2027,183 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
1886
2027
|
if (preview)
|
|
1887
2028
|
await sendMessage(preview);
|
|
1888
2029
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2030
|
+
if (wantsTodoOrCal && blockedWorkerTools.has(toolUse.name)) {
|
|
2031
|
+
result = {
|
|
2032
|
+
success: false,
|
|
2033
|
+
output: '',
|
|
2034
|
+
error: 'Do not use worker tools for /todo or /cal. Call todo_upsert_items or calendar_upsert_events directly.'
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
else
|
|
2038
|
+
switch (toolUse.name) {
|
|
2039
|
+
// New direct tools
|
|
2040
|
+
case 'bash':
|
|
2041
|
+
result = await this.bashTool.execute(input.command, input.timeout);
|
|
2042
|
+
break;
|
|
2043
|
+
case 'calendar_upsert_events':
|
|
2044
|
+
{
|
|
2045
|
+
const tagHint = this.getUserTagHint(this.lastUserMessage);
|
|
2046
|
+
const events = input.events || [];
|
|
2047
|
+
const taggedEvents = tagHint
|
|
2048
|
+
? events.map((event) => ({
|
|
2049
|
+
...event,
|
|
2050
|
+
title: event.title.includes(tagHint) ? event.title : `${event.title} ${tagHint}`
|
|
2051
|
+
}))
|
|
2052
|
+
: events;
|
|
2053
|
+
result = this.calendarTools.upsertCalendarEvents(taggedEvents, input.calendars);
|
|
2054
|
+
}
|
|
2055
|
+
break;
|
|
2056
|
+
case 'todo_upsert_items':
|
|
2057
|
+
{
|
|
2058
|
+
const tagHint = this.getUserTagHint(this.lastUserMessage);
|
|
2059
|
+
let items = input.items || [];
|
|
2060
|
+
const breakdownTarget = this.getBreakdownTarget(this.lastUserMessage);
|
|
2061
|
+
if (breakdownTarget) {
|
|
2062
|
+
items = this.ensureBreakdownOriginalDone(items, breakdownTarget);
|
|
2063
|
+
}
|
|
2064
|
+
const taggedItems = tagHint
|
|
2065
|
+
? items.map((item) => ({
|
|
2066
|
+
...item,
|
|
2067
|
+
title: item.title.includes(tagHint) ? item.title : `${item.title} ${tagHint}`
|
|
2068
|
+
}))
|
|
2069
|
+
: items;
|
|
2070
|
+
result = this.calendarTools.upsertTodoItems(taggedItems);
|
|
2071
|
+
}
|
|
2072
|
+
break;
|
|
2073
|
+
case 'web_search':
|
|
2074
|
+
result = await this.webTools.webSearch(input.query, { numResults: input.num_results });
|
|
2075
|
+
break;
|
|
2076
|
+
case 'fetch_url':
|
|
2077
|
+
result = await this.webTools.fetchUrl(input.url, {
|
|
2078
|
+
format: input.format,
|
|
2079
|
+
timeout: input.timeout
|
|
2080
|
+
});
|
|
2081
|
+
break;
|
|
2082
|
+
// Worker tools
|
|
2083
|
+
case 'spawn_worker': {
|
|
2084
|
+
const { task } = this.withAttachments(input.task);
|
|
2085
|
+
const fileScope = this.getFileScopeOverrides();
|
|
2086
|
+
const scopedTask = fileScope.instructions ? `${fileScope.instructions}\n\n${task}` : task;
|
|
2087
|
+
result = await this.workerTools.spawnWorker(scopedTask, {
|
|
2088
|
+
timeout: input.timeout,
|
|
2089
|
+
priority: input.priority,
|
|
2090
|
+
workingDirectory: input.working_directory || fileScope.workingDir
|
|
2091
|
+
});
|
|
1948
2092
|
break;
|
|
1949
2093
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2094
|
+
case 'check_worker':
|
|
2095
|
+
result = await this.workerTools.checkWorker(input.job_id);
|
|
2096
|
+
break;
|
|
2097
|
+
case 'wait_worker':
|
|
2098
|
+
result = await this.workerTools.waitWorker(input.job_id, input.max_wait);
|
|
2099
|
+
break;
|
|
2100
|
+
case 'list_workers':
|
|
2101
|
+
result = await this.workerTools.listWorkers(input.status);
|
|
2102
|
+
break;
|
|
2103
|
+
case 'cancel_worker':
|
|
2104
|
+
result = await this.workerTools.cancelWorker(input.job_id);
|
|
2105
|
+
break;
|
|
2106
|
+
// Legacy delegate tool
|
|
2107
|
+
case 'delegate_to_worker': {
|
|
2108
|
+
const { task, context } = this.withAttachments(input.task, input.context);
|
|
2109
|
+
const fileScope = this.getFileScopeOverrides();
|
|
2110
|
+
result = await this.delegateToWorker(task, context, input.working_directory || fileScope.workingDir, fileScope.instructions);
|
|
1965
2111
|
break;
|
|
1966
2112
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2113
|
+
case 'remember':
|
|
2114
|
+
result = await this.executeRemember(input.content, input.type, input.importance);
|
|
2115
|
+
break;
|
|
2116
|
+
case 'remember_entity': {
|
|
2117
|
+
const scope = input.scope === 'org' ? 'org' : 'user';
|
|
2118
|
+
const graph = scope === 'org' ? this.orgContextGraph : this.contextGraph;
|
|
2119
|
+
if (!graph) {
|
|
2120
|
+
result = { success: false, output: 'Org context is not configured for this agent.' };
|
|
2121
|
+
break;
|
|
2122
|
+
}
|
|
2123
|
+
const entity = await graph.upsertEntity({
|
|
2124
|
+
kind: input.kind,
|
|
2125
|
+
name: input.name,
|
|
2126
|
+
description: input.description,
|
|
2127
|
+
attributes: input.attributes,
|
|
2128
|
+
tags: input.tags
|
|
2129
|
+
});
|
|
2130
|
+
result = { success: true, output: `Stored ${scope} entity: ${entity.kind} ${entity.name} (${entity.key})` };
|
|
1983
2131
|
break;
|
|
1984
2132
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2133
|
+
case 'remember_relation': {
|
|
2134
|
+
const scope = input.scope === 'org' ? 'org' : 'user';
|
|
2135
|
+
const graph = scope === 'org' ? this.orgContextGraph : this.contextGraph;
|
|
2136
|
+
if (!graph) {
|
|
2137
|
+
result = { success: false, output: 'Org context is not configured for this agent.' };
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
const relation = await graph.linkEntities({
|
|
2141
|
+
from: input.from,
|
|
2142
|
+
to: input.to,
|
|
2143
|
+
type: input.type,
|
|
2144
|
+
description: input.description,
|
|
2145
|
+
fromKind: input.from_kind,
|
|
2146
|
+
toKind: input.to_kind
|
|
2147
|
+
});
|
|
2148
|
+
result = { success: true, output: `Linked ${scope}: ${relation.from} ${relation.type} ${relation.to}` };
|
|
1988
2149
|
break;
|
|
1989
2150
|
}
|
|
1990
|
-
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2151
|
+
case 'search_context': {
|
|
2152
|
+
const scope = input.scope === 'org' ? 'org' : 'user';
|
|
2153
|
+
const graph = scope === 'org' ? this.orgContextGraph : this.contextGraph;
|
|
2154
|
+
if (!graph) {
|
|
2155
|
+
result = { success: false, output: 'Org context is not configured for this agent.' };
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
const entities = await graph.searchEntities(input.query, { kind: input.kind, limit: 6 });
|
|
2159
|
+
if (entities.length === 0) {
|
|
2160
|
+
result = { success: true, output: 'No context entities found.' };
|
|
2161
|
+
break;
|
|
2162
|
+
}
|
|
2163
|
+
const lines = entities.map(entity => {
|
|
2164
|
+
const desc = entity.description ? ` - ${entity.description}` : '';
|
|
2165
|
+
return `[${entity.kind}] ${entity.name}${desc} (${entity.key})`;
|
|
2166
|
+
});
|
|
2167
|
+
result = { success: true, output: lines.join('\n') };
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
case 'search_memory':
|
|
2171
|
+
result = await this.executeSearchMemory(input.query, input.type);
|
|
2172
|
+
break;
|
|
2173
|
+
case 'schedule_task':
|
|
2174
|
+
result = await this.executeScheduleTask(input.task, input.when);
|
|
2175
|
+
break;
|
|
2176
|
+
case 'list_scheduled':
|
|
2177
|
+
result = await this.executeListScheduled();
|
|
2178
|
+
break;
|
|
2179
|
+
case 'memory_stats':
|
|
2180
|
+
result = await this.executeMemoryStats();
|
|
2181
|
+
break;
|
|
2182
|
+
case 'open_browser':
|
|
2183
|
+
result = await this.executeOpenBrowser(input.url);
|
|
2184
|
+
break;
|
|
2185
|
+
case 'start_local_server':
|
|
2186
|
+
result = await this.executeStartServer(input.directory, input.port);
|
|
2187
|
+
break;
|
|
2188
|
+
case 'stop_local_server':
|
|
2189
|
+
result = await this.executeStopServer(input.port);
|
|
2190
|
+
break;
|
|
2191
|
+
// Lia's internal task management tools
|
|
2192
|
+
case 'lia_plan':
|
|
2193
|
+
result = this.executeLiaPlan(input.goal, input.steps);
|
|
2194
|
+
break;
|
|
2195
|
+
case 'lia_add_task':
|
|
2196
|
+
result = this.executeLiaAddTask(input.task, input.priority);
|
|
2197
|
+
break;
|
|
2198
|
+
case 'lia_get_queue':
|
|
2199
|
+
result = this.executeLiaGetQueue();
|
|
2200
|
+
break;
|
|
2201
|
+
case 'lia_todo_write':
|
|
2202
|
+
result = this.executeLiaTodoWrite(input.todos);
|
|
2203
|
+
break;
|
|
2204
|
+
default:
|
|
2205
|
+
result = { success: false, output: `Unknown tool: ${toolUse.name}` };
|
|
1996
2206
|
}
|
|
1997
|
-
case 'search_memory':
|
|
1998
|
-
result = await this.executeSearchMemory(input.query, input.type);
|
|
1999
|
-
break;
|
|
2000
|
-
case 'schedule_task':
|
|
2001
|
-
result = await this.executeScheduleTask(input.task, input.when);
|
|
2002
|
-
break;
|
|
2003
|
-
case 'list_scheduled':
|
|
2004
|
-
result = await this.executeListScheduled();
|
|
2005
|
-
break;
|
|
2006
|
-
case 'memory_stats':
|
|
2007
|
-
result = await this.executeMemoryStats();
|
|
2008
|
-
break;
|
|
2009
|
-
case 'open_browser':
|
|
2010
|
-
result = await this.executeOpenBrowser(input.url);
|
|
2011
|
-
break;
|
|
2012
|
-
case 'start_local_server':
|
|
2013
|
-
result = await this.executeStartServer(input.directory, input.port);
|
|
2014
|
-
break;
|
|
2015
|
-
case 'stop_local_server':
|
|
2016
|
-
result = await this.executeStopServer(input.port);
|
|
2017
|
-
break;
|
|
2018
|
-
// Lia's internal task management tools
|
|
2019
|
-
case 'lia_plan':
|
|
2020
|
-
result = this.executeLiaPlan(input.goal, input.steps);
|
|
2021
|
-
break;
|
|
2022
|
-
case 'lia_add_task':
|
|
2023
|
-
result = this.executeLiaAddTask(input.task, input.priority);
|
|
2024
|
-
break;
|
|
2025
|
-
case 'lia_get_queue':
|
|
2026
|
-
result = this.executeLiaGetQueue();
|
|
2027
|
-
break;
|
|
2028
|
-
case 'lia_todo_write':
|
|
2029
|
-
result = this.executeLiaTodoWrite(input.todos);
|
|
2030
|
-
break;
|
|
2031
|
-
default:
|
|
2032
|
-
result = { success: false, output: `Unknown tool: ${toolUse.name}` };
|
|
2033
|
-
}
|
|
2034
2207
|
// Anthropic API requires content to be non-empty when is_error is true
|
|
2035
2208
|
// Belt-and-suspenders: ensure content is NEVER empty
|
|
2036
2209
|
let content = result.output || '';
|
package/dist/tools/worker.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { GalleryCallback, GalleryWorker };
|
|
|
8
8
|
export type { WorkerLogCallback };
|
|
9
9
|
export declare class WorkerTools {
|
|
10
10
|
private manager;
|
|
11
|
+
private workspaceDir;
|
|
11
12
|
constructor(workspaceDir: string);
|
|
12
13
|
/**
|
|
13
14
|
* Set callback for gallery updates (worker assets for UI)
|
package/dist/tools/worker.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
import { WorkerManager } from '../workers/manager.js';
|
|
6
6
|
export class WorkerTools {
|
|
7
7
|
manager;
|
|
8
|
+
workspaceDir;
|
|
8
9
|
constructor(workspaceDir) {
|
|
9
10
|
this.manager = new WorkerManager(workspaceDir);
|
|
11
|
+
this.workspaceDir = workspaceDir;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Set callback for gallery updates (worker assets for UI)
|
|
@@ -34,7 +36,7 @@ export class WorkerTools {
|
|
|
34
36
|
const jobId = await this.manager.spawn(task, {
|
|
35
37
|
timeout: options?.timeout,
|
|
36
38
|
priority: options?.priority,
|
|
37
|
-
workspace: options?.workingDirectory
|
|
39
|
+
workspace: options?.workingDirectory || this.workspaceDir
|
|
38
40
|
});
|
|
39
41
|
const job = this.manager.get(jobId);
|
|
40
42
|
return {
|
package/dist/websocket.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AssetResponse } from './core/asset-api.js';
|
|
|
11
11
|
export type MessageHandler = (message: WebSocketMessage) => Promise<void>;
|
|
12
12
|
export type StreamHandler = (chunk: string) => void;
|
|
13
13
|
export interface WebSocketMessage {
|
|
14
|
-
type: 'message' | 'interrupt' | 'ping' | 'pong' | 'connected' | 'asset_request';
|
|
14
|
+
type: 'message' | 'interrupt' | 'ping' | 'pong' | 'connected' | 'asset_request' | 'ready';
|
|
15
15
|
id?: string;
|
|
16
16
|
content?: string;
|
|
17
17
|
timestamp?: number;
|
|
@@ -37,6 +37,8 @@ export declare class AgentWebSocket {
|
|
|
37
37
|
private isConnected;
|
|
38
38
|
private pendingResponses;
|
|
39
39
|
private cloudflareBlocked;
|
|
40
|
+
private pendingMessages;
|
|
41
|
+
private readyPending;
|
|
40
42
|
constructor();
|
|
41
43
|
/**
|
|
42
44
|
* Connect to the WebSocket server
|
|
@@ -46,6 +48,7 @@ export declare class AgentWebSocket {
|
|
|
46
48
|
* Set handler for incoming messages
|
|
47
49
|
*/
|
|
48
50
|
onMessage(handler: MessageHandler): void;
|
|
51
|
+
sendReady(): void;
|
|
49
52
|
/**
|
|
50
53
|
* Send a streaming response for a message
|
|
51
54
|
*/
|
package/dist/websocket.js
CHANGED
|
@@ -21,6 +21,8 @@ export class AgentWebSocket {
|
|
|
21
21
|
isConnected = false;
|
|
22
22
|
pendingResponses = new Map();
|
|
23
23
|
cloudflareBlocked = false; // Track if Cloudflare is blocking WebSockets
|
|
24
|
+
pendingMessages = [];
|
|
25
|
+
readyPending = false;
|
|
24
26
|
constructor() {
|
|
25
27
|
const httpUrl = getServerUrl();
|
|
26
28
|
// Check for WebSocket-specific URL (bypasses Cloudflare)
|
|
@@ -51,6 +53,12 @@ export class AgentWebSocket {
|
|
|
51
53
|
this.isConnected = true;
|
|
52
54
|
this.reconnectAttempts = 0;
|
|
53
55
|
this.startPingInterval();
|
|
56
|
+
if (this.messageHandler) {
|
|
57
|
+
this.sendReady();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.readyPending = true;
|
|
61
|
+
}
|
|
54
62
|
resolve(true);
|
|
55
63
|
});
|
|
56
64
|
this.ws.on('message', (data) => {
|
|
@@ -99,6 +107,20 @@ export class AgentWebSocket {
|
|
|
99
107
|
*/
|
|
100
108
|
onMessage(handler) {
|
|
101
109
|
this.messageHandler = handler;
|
|
110
|
+
if (this.isConnected && this.readyPending) {
|
|
111
|
+
this.readyPending = false;
|
|
112
|
+
this.sendReady();
|
|
113
|
+
}
|
|
114
|
+
if (this.pendingMessages.length > 0) {
|
|
115
|
+
const queued = [...this.pendingMessages];
|
|
116
|
+
this.pendingMessages = [];
|
|
117
|
+
for (const message of queued) {
|
|
118
|
+
void handler(message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
sendReady() {
|
|
123
|
+
this.sendToServer({ type: 'ready' });
|
|
102
124
|
}
|
|
103
125
|
/**
|
|
104
126
|
* Send a streaming response for a message
|
|
@@ -251,6 +273,9 @@ export class AgentWebSocket {
|
|
|
251
273
|
if (this.messageHandler && message.content) {
|
|
252
274
|
this.messageHandler(message);
|
|
253
275
|
}
|
|
276
|
+
else if (message.content) {
|
|
277
|
+
this.pendingMessages.push(message);
|
|
278
|
+
}
|
|
254
279
|
break;
|
|
255
280
|
case 'asset_request':
|
|
256
281
|
// Handle asset requests (fast-path, no LLM)
|