@siftd/connect-agent 0.2.50 → 0.2.51

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.
@@ -207,6 +207,7 @@ export declare class MasterOrchestrator {
207
207
  private getTeamFilesDir;
208
208
  private getFileScopeOverrides;
209
209
  private rewriteFilesAlias;
210
+ private resolveFilesWritePath;
210
211
  private getFileScopeSystemNote;
211
212
  /**
212
213
  * Check if verbose mode is enabled
@@ -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 } 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';
@@ -103,7 +103,8 @@ TOOL RULES:
103
103
  - Installing packages
104
104
  - Running builds or tests
105
105
 
106
- delegate_to_worker / spawn_worker - ALL file operations:
106
+ files_write for simple /files writes
107
+ ✅ delegate_to_worker / spawn_worker - complex file operations:
107
108
  - Creating, editing, deleting files
108
109
  - Running npm/pip/cargo install
109
110
  - Building, testing, deploying
@@ -142,6 +143,7 @@ FILES BROWSER:
142
143
  Users can type /files to open the Finder UI (cloud mode).
143
144
  When asked to browse or locate files, point them to /files.
144
145
  Refer to /files instead of internal Lia-Hub paths in responses.
146
+ When users ask you to create or update a file in /files, use files_write.
145
147
 
146
148
  FILES SCOPES:
147
149
  - "My Files" are private to the user (default /files view)
@@ -186,7 +188,7 @@ WORKFLOW:
186
188
  Before complex work: Check CLAUDE.md → Read LANDMARKS.md → Search memory
187
189
  After completing work: Update LANDMARKS.md → Remember learnings
188
190
 
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).`;
191
+ 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
192
  const TODO_CAL_SYSTEM_PROMPT_BASE = `You are Lia. Your ONLY job is to update /todo and /cal using tools.
191
193
 
192
194
  Rules:
@@ -783,7 +785,7 @@ export class MasterOrchestrator {
783
785
  return { type: 'tool', name: 'calendar_upsert_events' };
784
786
  }
785
787
  if (wantsFiles) {
786
- return { type: 'tool', name: 'delegate_to_worker' };
788
+ return { type: 'tool', name: 'files_write' };
787
789
  }
788
790
  return undefined;
789
791
  }
@@ -849,6 +851,22 @@ export class MasterOrchestrator {
849
851
  return `${targetDir}${suffix}`;
850
852
  });
851
853
  }
854
+ resolveFilesWritePath(rawPath) {
855
+ const trimmed = rawPath.trim();
856
+ if (!trimmed)
857
+ throw new Error('File path is required');
858
+ const teamDir = this.currentFileScope === 'team' ? this.getTeamFilesDir() : null;
859
+ const baseDir = teamDir || getSharedOutputPath();
860
+ const normalized = this.rewriteFilesAlias(trimmed);
861
+ const relative = normalized.replace(/^\/+/, '');
862
+ const candidate = normalized.startsWith(baseDir) ? normalized : join(baseDir, relative);
863
+ const resolved = resolve(candidate);
864
+ const baseResolved = resolve(baseDir);
865
+ if (resolved !== baseResolved && !resolved.startsWith(`${baseResolved}${sep}`)) {
866
+ throw new Error('Invalid file path');
867
+ }
868
+ return resolved;
869
+ }
852
870
  getFileScopeSystemNote() {
853
871
  if (this.currentFileScope !== 'team')
854
872
  return null;
@@ -889,25 +907,6 @@ export class MasterOrchestrator {
889
907
  this.attachmentContext = null;
890
908
  }
891
909
  }
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
910
  // DISABLED: Dumb regex extraction was creating garbage todos
912
911
  // Let the AI use calendar_upsert_events and todo_upsert_items tools properly
913
912
  // const quickWrite = this.tryHandleCalendarTodo(message);
@@ -1412,7 +1411,9 @@ ${hubContextStr}
1412
1411
  const needsNoWorkers = toolName === 'todo_upsert_items' || toolName === 'calendar_upsert_events';
1413
1412
  const followup = needsNoWorkers
1414
1413
  ? `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
- : `You must call the ${toolName} tool now. Use the user's request as the task details.`;
1414
+ : toolName === 'files_write'
1415
+ ? 'You must call the files_write tool now. Provide the file path under /files and the full content.'
1416
+ : `You must call the ${toolName} tool now. Use the user's request as the task details.`;
1416
1417
  currentMessages = [
1417
1418
  ...currentMessages,
1418
1419
  { role: 'assistant', content: response.content },
@@ -1573,6 +1574,26 @@ and preserve explicit due dates and priority from the user.`,
1573
1574
  required: ['items']
1574
1575
  }
1575
1576
  },
1577
+ {
1578
+ name: 'files_write',
1579
+ description: `Create or overwrite a file in /files.
1580
+
1581
+ Only write to /files (shared outputs). Use a filename or /files/path/to/file.ext.`,
1582
+ input_schema: {
1583
+ type: 'object',
1584
+ properties: {
1585
+ path: {
1586
+ type: 'string',
1587
+ description: 'Target file path (e.g. /files/report.txt or report.txt)'
1588
+ },
1589
+ content: {
1590
+ type: 'string',
1591
+ description: 'File contents to write'
1592
+ }
1593
+ },
1594
+ required: ['path', 'content']
1595
+ }
1596
+ },
1576
1597
  // Web tools
1577
1598
  {
1578
1599
  name: 'web_search',
@@ -2115,6 +2136,22 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2115
2136
  result = this.calendarTools.upsertTodoItems(taggedItems);
2116
2137
  }
2117
2138
  break;
2139
+ case 'files_write':
2140
+ {
2141
+ try {
2142
+ const pathValue = String(input.path || '').trim();
2143
+ const content = String(input.content || '');
2144
+ const targetPath = this.resolveFilesWritePath(pathValue);
2145
+ mkdirSync(dirname(targetPath), { recursive: true });
2146
+ writeFileSync(targetPath, content, 'utf8');
2147
+ result = { success: true, output: 'Saved file to /files.' };
2148
+ }
2149
+ catch (error) {
2150
+ const message = error instanceof Error ? error.message : String(error);
2151
+ result = { success: false, output: '', error: message };
2152
+ }
2153
+ }
2154
+ break;
2118
2155
  case 'web_search':
2119
2156
  result = await this.webTools.webSearch(input.query, { numResults: input.num_results });
2120
2157
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",