@siftd/connect-agent 0.2.50 → 0.2.52
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
CHANGED
|
@@ -49,6 +49,7 @@ export declare class MasterOrchestrator {
|
|
|
49
49
|
private client;
|
|
50
50
|
private model;
|
|
51
51
|
private maxTokens;
|
|
52
|
+
private toolMaxTokens;
|
|
52
53
|
private memory;
|
|
53
54
|
private contextGraph;
|
|
54
55
|
private orgMemory?;
|
|
@@ -202,11 +203,13 @@ export declare class MasterOrchestrator {
|
|
|
202
203
|
private getLocalTimeZone;
|
|
203
204
|
private getTodoCalSystemPrompt;
|
|
204
205
|
private getToolChoice;
|
|
206
|
+
private recordUsage;
|
|
205
207
|
private withAttachments;
|
|
206
208
|
private updateFileScope;
|
|
207
209
|
private getTeamFilesDir;
|
|
208
210
|
private getFileScopeOverrides;
|
|
209
211
|
private rewriteFilesAlias;
|
|
212
|
+
private resolveFilesWritePath;
|
|
210
213
|
private getFileScopeSystemNote;
|
|
211
214
|
/**
|
|
212
215
|
* Check if verbose mode is enabled
|
package/dist/orchestrator.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import Anthropic from '@anthropic-ai/sdk';
|
|
8
8
|
import { spawn, execSync } from 'child_process';
|
|
9
|
-
import { existsSync, readFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync } from 'fs';
|
|
10
|
+
import { join, resolve, dirname, sep } from 'path';
|
|
11
11
|
import { AdvancedMemoryStore } from './core/memory-advanced.js';
|
|
12
12
|
import { PostgresMemoryStore, isPostgresConfigured } from './core/memory-postgres.js';
|
|
13
13
|
import { TaskScheduler } from './core/scheduler.js';
|
|
@@ -22,6 +22,35 @@ import { getKnowledgeForPrompt } from './genesis/index.js';
|
|
|
22
22
|
import { loadHubContext, formatHubContext, logAction, logWorker, getSharedOutputPath } from './core/hub.js';
|
|
23
23
|
import { buildWorkerPrompt } from './prompts/worker-system.js';
|
|
24
24
|
import { LiaTaskQueue } from './core/task-queue.js';
|
|
25
|
+
const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-5-20251101';
|
|
26
|
+
const DEFAULT_TOOL_MAX_TOKENS = 1024;
|
|
27
|
+
function readBooleanEnv(value, fallback) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return fallback;
|
|
30
|
+
return !['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
|
|
31
|
+
}
|
|
32
|
+
function resolveClaudeModel() {
|
|
33
|
+
const raw = (process.env.LIA_MODEL || process.env.ANTHROPIC_MODEL || DEFAULT_CLAUDE_MODEL).trim();
|
|
34
|
+
if (!raw)
|
|
35
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
36
|
+
if (readBooleanEnv(process.env.LIA_FORCE_OPUS, true) && raw !== DEFAULT_CLAUDE_MODEL) {
|
|
37
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
38
|
+
}
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
function resolveClaudeBudgetUsd() {
|
|
42
|
+
const raw = process.env.LIA_MAX_BUDGET_USD || process.env.CLAUDE_MAX_BUDGET_USD;
|
|
43
|
+
if (!raw)
|
|
44
|
+
return null;
|
|
45
|
+
const trimmed = raw.trim();
|
|
46
|
+
return trimmed ? trimmed : null;
|
|
47
|
+
}
|
|
48
|
+
function resolveToolMaxTokens() {
|
|
49
|
+
const raw = Number(process.env.LIA_TOOL_MAX_TOKENS);
|
|
50
|
+
if (Number.isFinite(raw) && raw >= 256)
|
|
51
|
+
return Math.min(raw, 4096);
|
|
52
|
+
return DEFAULT_TOOL_MAX_TOKENS;
|
|
53
|
+
}
|
|
25
54
|
/**
|
|
26
55
|
* Extract file paths from worker output
|
|
27
56
|
* Workers naturally mention files they create: "Created /tmp/foo.html", "Saved to /path/file"
|
|
@@ -103,7 +132,8 @@ TOOL RULES:
|
|
|
103
132
|
- Installing packages
|
|
104
133
|
- Running builds or tests
|
|
105
134
|
|
|
106
|
-
✅
|
|
135
|
+
✅ files_write for simple /files writes
|
|
136
|
+
✅ delegate_to_worker / spawn_worker - complex file operations:
|
|
107
137
|
- Creating, editing, deleting files
|
|
108
138
|
- Running npm/pip/cargo install
|
|
109
139
|
- Building, testing, deploying
|
|
@@ -142,6 +172,7 @@ FILES BROWSER:
|
|
|
142
172
|
Users can type /files to open the Finder UI (cloud mode).
|
|
143
173
|
When asked to browse or locate files, point them to /files.
|
|
144
174
|
Refer to /files instead of internal Lia-Hub paths in responses.
|
|
175
|
+
When users ask you to create or update a file in /files, use files_write.
|
|
145
176
|
|
|
146
177
|
FILES SCOPES:
|
|
147
178
|
- "My Files" are private to the user (default /files view)
|
|
@@ -186,7 +217,7 @@ WORKFLOW:
|
|
|
186
217
|
Before complex work: Check CLAUDE.md → Read LANDMARKS.md → Search memory
|
|
187
218
|
After completing work: Update LANDMARKS.md → Remember learnings
|
|
188
219
|
|
|
189
|
-
You orchestrate through workers. You remember through memory. You never do arbitrary file operations directly (calendar_upsert_events and
|
|
220
|
+
You orchestrate through workers. You remember through memory. You never do arbitrary file operations directly (calendar_upsert_events, todo_upsert_items, and files_write are the safe exceptions).`;
|
|
190
221
|
const TODO_CAL_SYSTEM_PROMPT_BASE = `You are Lia. Your ONLY job is to update /todo and /cal using tools.
|
|
191
222
|
|
|
192
223
|
Rules:
|
|
@@ -201,6 +232,7 @@ export class MasterOrchestrator {
|
|
|
201
232
|
client;
|
|
202
233
|
model;
|
|
203
234
|
maxTokens;
|
|
235
|
+
toolMaxTokens;
|
|
204
236
|
memory;
|
|
205
237
|
contextGraph;
|
|
206
238
|
orgMemory;
|
|
@@ -242,8 +274,14 @@ export class MasterOrchestrator {
|
|
|
242
274
|
todoUpdateCallback;
|
|
243
275
|
constructor(options) {
|
|
244
276
|
this.client = new Anthropic({ apiKey: options.apiKey });
|
|
245
|
-
|
|
277
|
+
const requestedModel = options.model?.trim();
|
|
278
|
+
this.model = requestedModel
|
|
279
|
+
? (readBooleanEnv(process.env.LIA_FORCE_OPUS, true) && requestedModel !== DEFAULT_CLAUDE_MODEL
|
|
280
|
+
? DEFAULT_CLAUDE_MODEL
|
|
281
|
+
: requestedModel)
|
|
282
|
+
: resolveClaudeModel();
|
|
246
283
|
this.maxTokens = options.maxTokens || 4096;
|
|
284
|
+
this.toolMaxTokens = resolveToolMaxTokens();
|
|
247
285
|
this.userId = options.userId;
|
|
248
286
|
this.orgId = options.orgId;
|
|
249
287
|
this.orgRole = options.orgRole;
|
|
@@ -696,7 +734,7 @@ export class MasterOrchestrator {
|
|
|
696
734
|
}
|
|
697
735
|
hasCalendarMutation(message) {
|
|
698
736
|
const lower = this.stripTodoSnapshot(message).toLowerCase();
|
|
699
|
-
const target = /(^|\s)\/cal\b|\/calendar\b|\bcalendar\b/.test(lower);
|
|
737
|
+
const target = /(^|\s)\/cal\b|\/calendar\b|\bcalendar\b|\bcal\b/.test(lower);
|
|
700
738
|
const action = /\b(add|create|schedule|book|move|reschedule|update|change|cancel|delete|remove)\b/.test(lower);
|
|
701
739
|
const query = /\b(what|show|list|open|view|see)\b/.test(lower);
|
|
702
740
|
return target && action && !query;
|
|
@@ -783,10 +821,36 @@ export class MasterOrchestrator {
|
|
|
783
821
|
return { type: 'tool', name: 'calendar_upsert_events' };
|
|
784
822
|
}
|
|
785
823
|
if (wantsFiles) {
|
|
786
|
-
return { type: 'tool', name: '
|
|
824
|
+
return { type: 'tool', name: 'files_write' };
|
|
787
825
|
}
|
|
788
826
|
return undefined;
|
|
789
827
|
}
|
|
828
|
+
recordUsage(response, meta) {
|
|
829
|
+
const usage = response.usage;
|
|
830
|
+
if (!usage)
|
|
831
|
+
return;
|
|
832
|
+
try {
|
|
833
|
+
const usageDir = join(getSharedOutputPath(), '.lia');
|
|
834
|
+
mkdirSync(usageDir, { recursive: true });
|
|
835
|
+
const entry = {
|
|
836
|
+
ts: new Date().toISOString(),
|
|
837
|
+
model: this.model,
|
|
838
|
+
input_tokens: usage.input_tokens,
|
|
839
|
+
output_tokens: usage.output_tokens,
|
|
840
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
841
|
+
cache_read_input_tokens: usage.cache_read_input_tokens,
|
|
842
|
+
tool_choice: meta.toolChoice || null,
|
|
843
|
+
iterations: meta.iterations,
|
|
844
|
+
messages: meta.messages,
|
|
845
|
+
stop_reason: response.stop_reason || null,
|
|
846
|
+
response_id: response.id || null,
|
|
847
|
+
};
|
|
848
|
+
appendFileSync(join(usageDir, 'usage.jsonl'), `${JSON.stringify(entry)}\n`, 'utf8');
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
console.warn('[ORCHESTRATOR] Usage log failed:', error);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
790
854
|
withAttachments(task, context) {
|
|
791
855
|
if (!this.attachmentContext)
|
|
792
856
|
return { task, context };
|
|
@@ -849,6 +913,22 @@ export class MasterOrchestrator {
|
|
|
849
913
|
return `${targetDir}${suffix}`;
|
|
850
914
|
});
|
|
851
915
|
}
|
|
916
|
+
resolveFilesWritePath(rawPath) {
|
|
917
|
+
const trimmed = rawPath.trim();
|
|
918
|
+
if (!trimmed)
|
|
919
|
+
throw new Error('File path is required');
|
|
920
|
+
const teamDir = this.currentFileScope === 'team' ? this.getTeamFilesDir() : null;
|
|
921
|
+
const baseDir = teamDir || getSharedOutputPath();
|
|
922
|
+
const normalized = this.rewriteFilesAlias(trimmed);
|
|
923
|
+
const relative = normalized.replace(/^\/+/, '');
|
|
924
|
+
const candidate = normalized.startsWith(baseDir) ? normalized : join(baseDir, relative);
|
|
925
|
+
const resolved = resolve(candidate);
|
|
926
|
+
const baseResolved = resolve(baseDir);
|
|
927
|
+
if (resolved !== baseResolved && !resolved.startsWith(`${baseResolved}${sep}`)) {
|
|
928
|
+
throw new Error('Invalid file path');
|
|
929
|
+
}
|
|
930
|
+
return resolved;
|
|
931
|
+
}
|
|
852
932
|
getFileScopeSystemNote() {
|
|
853
933
|
if (this.currentFileScope !== 'team')
|
|
854
934
|
return null;
|
|
@@ -889,25 +969,6 @@ export class MasterOrchestrator {
|
|
|
889
969
|
this.attachmentContext = null;
|
|
890
970
|
}
|
|
891
971
|
}
|
|
892
|
-
const wantsFiles = this.hasFileMutation(message);
|
|
893
|
-
if (wantsFiles) {
|
|
894
|
-
this.attachmentContext = this.extractAttachmentContext(message);
|
|
895
|
-
const { task } = this.withAttachments(message);
|
|
896
|
-
const normalizedTask = this.rewriteFilesAlias(task);
|
|
897
|
-
const fileScope = this.getFileScopeOverrides();
|
|
898
|
-
const scopedTask = fileScope.instructions ? `${fileScope.instructions}\n\n${normalizedTask}` : normalizedTask;
|
|
899
|
-
try {
|
|
900
|
-
await this.delegateToWorker(scopedTask, undefined, fileScope.workingDir, fileScope.instructions);
|
|
901
|
-
return 'Working on it. Check /files shortly.';
|
|
902
|
-
}
|
|
903
|
-
catch (error) {
|
|
904
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
905
|
-
return `Error: ${errorMessage}`;
|
|
906
|
-
}
|
|
907
|
-
finally {
|
|
908
|
-
this.attachmentContext = null;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
972
|
// DISABLED: Dumb regex extraction was creating garbage todos
|
|
912
973
|
// Let the AI use calendar_upsert_events and todo_upsert_items tools properly
|
|
913
974
|
// const quickWrite = this.tryHandleCalendarTodo(message);
|
|
@@ -1386,12 +1447,13 @@ ${hubContextStr}
|
|
|
1386
1447
|
while (iterations < maxIterations) {
|
|
1387
1448
|
iterations++;
|
|
1388
1449
|
const toolChoice = forcedToolChoice ?? this.getToolChoice(currentMessages);
|
|
1450
|
+
const requestMaxTokens = wantsTodoOrCal ? this.toolMaxTokens : this.maxTokens;
|
|
1389
1451
|
const requestStart = Date.now();
|
|
1390
1452
|
console.log(`[ORCHESTRATOR] Anthropic request ${iterations}/${maxIterations} (model: ${this.model})`);
|
|
1391
1453
|
const response = await Promise.race([
|
|
1392
1454
|
this.client.messages.create({
|
|
1393
1455
|
model: this.model,
|
|
1394
|
-
max_tokens:
|
|
1456
|
+
max_tokens: requestMaxTokens,
|
|
1395
1457
|
system,
|
|
1396
1458
|
tools,
|
|
1397
1459
|
messages: currentMessages,
|
|
@@ -1404,6 +1466,11 @@ ${hubContextStr}
|
|
|
1404
1466
|
})
|
|
1405
1467
|
]);
|
|
1406
1468
|
console.log(`[ORCHESTRATOR] Anthropic response ${iterations}/${maxIterations} in ${Date.now() - requestStart}ms`);
|
|
1469
|
+
this.recordUsage(response, {
|
|
1470
|
+
iterations,
|
|
1471
|
+
messages: currentMessages.length,
|
|
1472
|
+
toolChoice: toolChoice?.name
|
|
1473
|
+
});
|
|
1407
1474
|
// Check if done
|
|
1408
1475
|
if (response.stop_reason === 'end_turn' || !this.hasToolUse(response.content)) {
|
|
1409
1476
|
if (forcedToolChoice && !retriedForcedTool) {
|
|
@@ -1412,7 +1479,9 @@ ${hubContextStr}
|
|
|
1412
1479
|
const needsNoWorkers = toolName === 'todo_upsert_items' || toolName === 'calendar_upsert_events';
|
|
1413
1480
|
const followup = needsNoWorkers
|
|
1414
1481
|
? `You must call the ${toolName} tool now. Use the exact task/event titles and any bracketed tags exactly as provided. Do not spawn workers.`
|
|
1415
|
-
:
|
|
1482
|
+
: toolName === 'files_write'
|
|
1483
|
+
? 'You must call the files_write tool now. Provide the file path under /files and the full content.'
|
|
1484
|
+
: `You must call the ${toolName} tool now. Use the user's request as the task details.`;
|
|
1416
1485
|
currentMessages = [
|
|
1417
1486
|
...currentMessages,
|
|
1418
1487
|
{ role: 'assistant', content: response.content },
|
|
@@ -1573,6 +1642,26 @@ and preserve explicit due dates and priority from the user.`,
|
|
|
1573
1642
|
required: ['items']
|
|
1574
1643
|
}
|
|
1575
1644
|
},
|
|
1645
|
+
{
|
|
1646
|
+
name: 'files_write',
|
|
1647
|
+
description: `Create or overwrite a file in /files.
|
|
1648
|
+
|
|
1649
|
+
Only write to /files (shared outputs). Use a filename or /files/path/to/file.ext.`,
|
|
1650
|
+
input_schema: {
|
|
1651
|
+
type: 'object',
|
|
1652
|
+
properties: {
|
|
1653
|
+
path: {
|
|
1654
|
+
type: 'string',
|
|
1655
|
+
description: 'Target file path (e.g. /files/report.txt or report.txt)'
|
|
1656
|
+
},
|
|
1657
|
+
content: {
|
|
1658
|
+
type: 'string',
|
|
1659
|
+
description: 'File contents to write'
|
|
1660
|
+
}
|
|
1661
|
+
},
|
|
1662
|
+
required: ['path', 'content']
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1576
1665
|
// Web tools
|
|
1577
1666
|
{
|
|
1578
1667
|
name: 'web_search',
|
|
@@ -2115,6 +2204,22 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2115
2204
|
result = this.calendarTools.upsertTodoItems(taggedItems);
|
|
2116
2205
|
}
|
|
2117
2206
|
break;
|
|
2207
|
+
case 'files_write':
|
|
2208
|
+
{
|
|
2209
|
+
try {
|
|
2210
|
+
const pathValue = String(input.path || '').trim();
|
|
2211
|
+
const content = String(input.content || '');
|
|
2212
|
+
const targetPath = this.resolveFilesWritePath(pathValue);
|
|
2213
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
2214
|
+
writeFileSync(targetPath, content, 'utf8');
|
|
2215
|
+
result = { success: true, output: 'Saved file to /files.' };
|
|
2216
|
+
}
|
|
2217
|
+
catch (error) {
|
|
2218
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2219
|
+
result = { success: false, output: '', error: message };
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
break;
|
|
2118
2223
|
case 'web_search':
|
|
2119
2224
|
result = await this.webTools.webSearch(input.query, { numResults: input.num_results });
|
|
2120
2225
|
break;
|
|
@@ -2310,7 +2415,9 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2310
2415
|
};
|
|
2311
2416
|
// Escape single quotes in prompt for shell safety
|
|
2312
2417
|
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
|
2313
|
-
const
|
|
2418
|
+
const budget = resolveClaudeBudgetUsd();
|
|
2419
|
+
const budgetArg = budget ? ` --max-budget-usd ${budget}` : '';
|
|
2420
|
+
const claudeCmd = `${this.claudePath} -p '${escapedPrompt}' --output-format text --dangerously-skip-permissions --model ${this.model}${budgetArg}`;
|
|
2314
2421
|
const isRoot = process.getuid?.() === 0;
|
|
2315
2422
|
const shellCmd = isRoot
|
|
2316
2423
|
? `runuser -u lia -m -- /bin/bash -l -c "${claudeCmd.replace(/"/g, '\\"')}"`
|
|
@@ -12,7 +12,7 @@ export declare const WORKER_IDENTITY = "## Worker Identity\nYou are a Claude Cod
|
|
|
12
12
|
/**
|
|
13
13
|
* Output format workers should follow
|
|
14
14
|
*/
|
|
15
|
-
export declare const WORKER_OUTPUT_FORMAT = "## Output Format\nWhen done, clearly list:\n- Files created: /path/to/file.ext\n- Files modified: /path/to/file.ext\n- Key findings:
|
|
15
|
+
export declare const WORKER_OUTPUT_FORMAT = "## Output Format\nWhen done, clearly list:\n- Files created: /path/to/file.ext\n- Files modified: /path/to/file.ext\n- Key findings: 1-3 short bullets\n\nBe concise. Do not restate the task.";
|
|
16
16
|
/**
|
|
17
17
|
* Instructions for workers to contribute back to memory
|
|
18
18
|
* Workers can add to orchestrator's memory using these patterns
|
|
@@ -44,9 +44,9 @@ export const WORKER_OUTPUT_FORMAT = `## Output Format
|
|
|
44
44
|
When done, clearly list:
|
|
45
45
|
- Files created: /path/to/file.ext
|
|
46
46
|
- Files modified: /path/to/file.ext
|
|
47
|
-
- Key findings:
|
|
47
|
+
- Key findings: 1-3 short bullets
|
|
48
48
|
|
|
49
|
-
Be concise.
|
|
49
|
+
Be concise. Do not restate the task.`;
|
|
50
50
|
/**
|
|
51
51
|
* Instructions for workers to contribute back to memory
|
|
52
52
|
* Workers can add to orchestrator's memory using these patterns
|
|
@@ -71,6 +71,15 @@ Share data with other workers:
|
|
|
71
71
|
*/
|
|
72
72
|
export function getWorkerLoggingInstructions(jobId) {
|
|
73
73
|
const logFile = `/tmp/worker-${jobId}-log.txt`;
|
|
74
|
+
const verbose = process.env.LIA_WORKER_VERBOSE === '1';
|
|
75
|
+
if (!verbose) {
|
|
76
|
+
return `## Progress & Logging
|
|
77
|
+
- Keep output minimal; report only key steps and final results
|
|
78
|
+
|
|
79
|
+
REQUIRED - Log Export:
|
|
80
|
+
At the END of your work, create a final log file at: ${logFile}
|
|
81
|
+
Include: job_id=${jobId}, timestamp, summary of work done, files modified, key findings.`;
|
|
82
|
+
}
|
|
74
83
|
return `## Progress & Logging
|
|
75
84
|
- Output findings as you go, don't wait until the end
|
|
76
85
|
- Print discoveries and insights immediately as you find them
|
package/dist/workers/manager.js
CHANGED
|
@@ -48,6 +48,28 @@ function getFileType(ext) {
|
|
|
48
48
|
return 'text';
|
|
49
49
|
return 'other';
|
|
50
50
|
}
|
|
51
|
+
const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-5-20251101';
|
|
52
|
+
function readBooleanEnv(value, fallback) {
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return fallback;
|
|
55
|
+
return !['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
function resolveClaudeModel() {
|
|
58
|
+
const raw = (process.env.LIA_MODEL || process.env.ANTHROPIC_MODEL || DEFAULT_CLAUDE_MODEL).trim();
|
|
59
|
+
if (!raw)
|
|
60
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
61
|
+
if (readBooleanEnv(process.env.LIA_FORCE_OPUS, true) && raw !== DEFAULT_CLAUDE_MODEL) {
|
|
62
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
63
|
+
}
|
|
64
|
+
return raw;
|
|
65
|
+
}
|
|
66
|
+
function resolveClaudeBudgetUsd() {
|
|
67
|
+
const raw = process.env.LIA_MAX_BUDGET_USD || process.env.CLAUDE_MAX_BUDGET_USD;
|
|
68
|
+
if (!raw)
|
|
69
|
+
return null;
|
|
70
|
+
const trimmed = raw.trim();
|
|
71
|
+
return trimmed ? trimmed : null;
|
|
72
|
+
}
|
|
51
73
|
export class WorkerManager {
|
|
52
74
|
config;
|
|
53
75
|
activeWorkers = new Map();
|
|
@@ -166,22 +188,30 @@ export class WorkerManager {
|
|
|
166
188
|
try {
|
|
167
189
|
// Add checkpoint and logging instructions to prevent data loss
|
|
168
190
|
const logFile = `/tmp/worker-${jobId}-log.txt`;
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
IMPORTANT - Progress & Logging:
|
|
191
|
+
const verbose = process.env.LIA_WORKER_VERBOSE === '1';
|
|
192
|
+
const loggingBlock = verbose
|
|
193
|
+
? `IMPORTANT - Progress & Logging:
|
|
172
194
|
- Output findings as you go, don't wait until the end
|
|
173
195
|
- Print discoveries and insights immediately as you find them
|
|
174
196
|
- Report on each file/step before moving to the next
|
|
175
|
-
- Do NOT open browsers or start servers - just create files and report paths
|
|
197
|
+
- Do NOT open browsers or start servers - just create files and report paths`
|
|
198
|
+
: `IMPORTANT - Progress & Logging:
|
|
199
|
+
- Keep output minimal; report only key steps and final results
|
|
200
|
+
- Do NOT open browsers or start servers - just create files and report paths`;
|
|
201
|
+
const enhancedTask = `${task}
|
|
202
|
+
|
|
203
|
+
${loggingBlock}
|
|
176
204
|
|
|
177
205
|
REQUIRED - Log Export:
|
|
178
206
|
At the END of your work, create a final log file at: ${logFile}
|
|
179
|
-
Include: job_id=${jobId}, timestamp, summary of work done, files modified, key findings
|
|
180
|
-
This ensures nothing is lost even if your output gets truncated.`;
|
|
207
|
+
Include: job_id=${jobId}, timestamp, summary of work done, files modified, key findings.`;
|
|
181
208
|
// Escape single quotes in task for shell safety
|
|
182
209
|
const escapedTask = enhancedTask.replace(/'/g, "'\\''");
|
|
183
210
|
// Build the claude command
|
|
184
|
-
const
|
|
211
|
+
const model = resolveClaudeModel();
|
|
212
|
+
const budget = resolveClaudeBudgetUsd();
|
|
213
|
+
const budgetArg = budget ? ` --max-budget-usd ${budget}` : '';
|
|
214
|
+
const claudeCmd = `claude -p '${escapedTask}' --output-format text --dangerously-skip-permissions --model ${model}${budgetArg}`;
|
|
185
215
|
// If running as root, run workers as 'lia' user to avoid --dangerously-skip-permissions being blocked
|
|
186
216
|
// The Claude CLI blocks this flag for root users as a security measure
|
|
187
217
|
const isRoot = process.getuid?.() === 0;
|