@siftd/connect-agent 0.2.51 → 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.
@@ -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,6 +203,7 @@ 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;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import Anthropic from '@anthropic-ai/sdk';
8
8
  import { spawn, execSync } from 'child_process';
9
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
9
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync } from 'fs';
10
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';
@@ -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"
@@ -203,6 +232,7 @@ export class MasterOrchestrator {
203
232
  client;
204
233
  model;
205
234
  maxTokens;
235
+ toolMaxTokens;
206
236
  memory;
207
237
  contextGraph;
208
238
  orgMemory;
@@ -244,8 +274,14 @@ export class MasterOrchestrator {
244
274
  todoUpdateCallback;
245
275
  constructor(options) {
246
276
  this.client = new Anthropic({ apiKey: options.apiKey });
247
- this.model = options.model || process.env.ANTHROPIC_MODEL || 'claude-opus-4-5-20251101';
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();
248
283
  this.maxTokens = options.maxTokens || 4096;
284
+ this.toolMaxTokens = resolveToolMaxTokens();
249
285
  this.userId = options.userId;
250
286
  this.orgId = options.orgId;
251
287
  this.orgRole = options.orgRole;
@@ -698,7 +734,7 @@ export class MasterOrchestrator {
698
734
  }
699
735
  hasCalendarMutation(message) {
700
736
  const lower = this.stripTodoSnapshot(message).toLowerCase();
701
- 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);
702
738
  const action = /\b(add|create|schedule|book|move|reschedule|update|change|cancel|delete|remove)\b/.test(lower);
703
739
  const query = /\b(what|show|list|open|view|see)\b/.test(lower);
704
740
  return target && action && !query;
@@ -789,6 +825,32 @@ export class MasterOrchestrator {
789
825
  }
790
826
  return undefined;
791
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
+ }
792
854
  withAttachments(task, context) {
793
855
  if (!this.attachmentContext)
794
856
  return { task, context };
@@ -1385,12 +1447,13 @@ ${hubContextStr}
1385
1447
  while (iterations < maxIterations) {
1386
1448
  iterations++;
1387
1449
  const toolChoice = forcedToolChoice ?? this.getToolChoice(currentMessages);
1450
+ const requestMaxTokens = wantsTodoOrCal ? this.toolMaxTokens : this.maxTokens;
1388
1451
  const requestStart = Date.now();
1389
1452
  console.log(`[ORCHESTRATOR] Anthropic request ${iterations}/${maxIterations} (model: ${this.model})`);
1390
1453
  const response = await Promise.race([
1391
1454
  this.client.messages.create({
1392
1455
  model: this.model,
1393
- max_tokens: this.maxTokens,
1456
+ max_tokens: requestMaxTokens,
1394
1457
  system,
1395
1458
  tools,
1396
1459
  messages: currentMessages,
@@ -1403,6 +1466,11 @@ ${hubContextStr}
1403
1466
  })
1404
1467
  ]);
1405
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
+ });
1406
1474
  // Check if done
1407
1475
  if (response.stop_reason === 'end_turn' || !this.hasToolUse(response.content)) {
1408
1476
  if (forcedToolChoice && !retriedForcedTool) {
@@ -2347,7 +2415,9 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2347
2415
  };
2348
2416
  // Escape single quotes in prompt for shell safety
2349
2417
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
2350
- const claudeCmd = `${this.claudePath} -p '${escapedPrompt}' --output-format text --dangerously-skip-permissions`;
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}`;
2351
2421
  const isRoot = process.getuid?.() === 0;
2352
2422
  const shellCmd = isRoot
2353
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: brief summary\n\nBe concise. Execute efficiently.";
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: brief summary
47
+ - Key findings: 1-3 short bullets
48
48
 
49
- Be concise. Execute efficiently.`;
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
@@ -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 enhancedTask = `${task}
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 claudeCmd = `claude -p '${escapedTask}' --output-format text --dangerously-skip-permissions`;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.51",
3
+ "version": "0.2.52",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",