@siftd/connect-agent 0.2.49 → 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;
@@ -1393,7 +1411,9 @@ ${hubContextStr}
1393
1411
  const needsNoWorkers = toolName === 'todo_upsert_items' || toolName === 'calendar_upsert_events';
1394
1412
  const followup = needsNoWorkers
1395
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.`
1396
- : `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.`;
1397
1417
  currentMessages = [
1398
1418
  ...currentMessages,
1399
1419
  { role: 'assistant', content: response.content },
@@ -1554,6 +1574,26 @@ and preserve explicit due dates and priority from the user.`,
1554
1574
  required: ['items']
1555
1575
  }
1556
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
+ },
1557
1597
  // Web tools
1558
1598
  {
1559
1599
  name: 'web_search',
@@ -2096,6 +2136,22 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2096
2136
  result = this.calendarTools.upsertTodoItems(taggedItems);
2097
2137
  }
2098
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;
2099
2155
  case 'web_search':
2100
2156
  result = await this.webTools.webSearch(input.query, { numResults: input.num_results });
2101
2157
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.49",
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",