@siftd/connect-agent 0.2.58 → 0.2.60

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/agent.js CHANGED
@@ -9,7 +9,7 @@ import { getUserId, getOrgId, getAnthropicApiKey, isCloudMode, getDeploymentInfo
9
9
  import { MasterOrchestrator } from './orchestrator.js';
10
10
  import { AgentWebSocket } from './websocket.js';
11
11
  import { startHeartbeat, stopHeartbeat, getHeartbeatState } from './heartbeat.js';
12
- import { loadHubContext, readScratchpad } from './core/hub.js';
12
+ import { getSharedOutputPath, loadHubContext, readScratchpad } from './core/hub.js';
13
13
  import { PRODUCT_FULL_NAME } from './branding.js';
14
14
  import { startPreviewWorker, stopPreviewWorker } from './core/preview-worker.js';
15
15
  function parseAttachments(content) {
@@ -38,6 +38,53 @@ function stripAttachmentBlock(content) {
38
38
  return content;
39
39
  return content.slice(0, index).trim();
40
40
  }
41
+ function normalizeAttachmentSubdir(raw, allowRoot) {
42
+ if (!raw)
43
+ return null;
44
+ let value = raw.trim();
45
+ value = value.replace(/^["'`]+|["'`]+$/g, '');
46
+ value = value.replace(/[),.;:!?]+$/g, '');
47
+ value = value.replace(/\\+/g, '/');
48
+ const filesPrefix = /^\/?files\b/i;
49
+ if (filesPrefix.test(value)) {
50
+ value = value.replace(filesPrefix, '');
51
+ }
52
+ value = value.replace(/^\/+/, '').replace(/\/+$/, '');
53
+ if (!value)
54
+ return allowRoot ? '' : null;
55
+ const segments = value
56
+ .split('/')
57
+ .map((segment) => segment.trim())
58
+ .filter((segment) => segment && segment !== '.' && segment !== '..');
59
+ const sanitized = segments
60
+ .map((segment) => segment.replace(/[^\w.-]+/g, '_'))
61
+ .filter(Boolean);
62
+ if (sanitized.length === 0)
63
+ return allowRoot ? '' : null;
64
+ return sanitized.join('/');
65
+ }
66
+ function extractAttachmentSubdir(text) {
67
+ const cleaned = text.trim();
68
+ if (!cleaned)
69
+ return null;
70
+ const filesMatch = cleaned.match(/\/files(?:\/[^\s"'`)\]]+)?/i);
71
+ if (filesMatch) {
72
+ return normalizeAttachmentSubdir(filesMatch[0], true);
73
+ }
74
+ const folderMatch = cleaned.match(/\b(?:folder|directory)\b[^a-zA-Z0-9]*(?:called|named)?\s*["'`]?([A-Za-z0-9][\w./-]+)\/?/i);
75
+ if (folderMatch) {
76
+ return normalizeAttachmentSubdir(folderMatch[1], false);
77
+ }
78
+ const inFolderMatch = cleaned.match(/\bin\s+([A-Za-z0-9][\w./-]+)\s*(?:folder|directory)\b/i);
79
+ if (inFolderMatch) {
80
+ return normalizeAttachmentSubdir(inFolderMatch[1], false);
81
+ }
82
+ const inPathMatch = cleaned.match(/\bin\s+([A-Za-z0-9][\w./-]+\/)\b/i);
83
+ if (inPathMatch) {
84
+ return normalizeAttachmentSubdir(inPathMatch[1], false);
85
+ }
86
+ return null;
87
+ }
41
88
  function isSaveIntent(text) {
42
89
  const lower = text.toLowerCase();
43
90
  if (!lower)
@@ -74,14 +121,17 @@ function humanizePath(path) {
74
121
  }
75
122
  return path;
76
123
  }
77
- async function saveAttachments(attachments, onProgress) {
78
- const home = process.env.HOME || '/tmp';
79
- const targetDir = join(home, 'Lia-Hub', 'shared', 'outputs', 'safe-files');
124
+ async function saveAttachments(attachments, options = {}) {
125
+ const baseDir = getSharedOutputPath();
126
+ const requestedSubdir = options.subdir;
127
+ const targetDir = typeof requestedSubdir === 'string'
128
+ ? join(baseDir, requestedSubdir)
129
+ : join(baseDir, 'safe-files');
80
130
  await mkdir(targetDir, { recursive: true });
81
131
  const results = [];
82
132
  for (const attachment of attachments) {
83
133
  const targetPath = await getUniquePath(targetDir, attachment.name);
84
- onProgress?.(`Saving ${attachment.name}...`);
134
+ options.onProgress?.(`Saving ${attachment.name}...`);
85
135
  try {
86
136
  const response = await fetch(attachment.url);
87
137
  if (!response.ok) {
@@ -93,12 +143,12 @@ async function saveAttachments(attachments, onProgress) {
93
143
  const stream = Readable.fromWeb(response.body);
94
144
  await pipeline(stream, createWriteStream(targetPath));
95
145
  results.push({ name: attachment.name, path: targetPath });
96
- onProgress?.(`Saved ${attachment.name} → ${humanizePath(targetPath)}`);
146
+ options.onProgress?.(`Saved ${attachment.name} → ${humanizePath(targetPath)}`);
97
147
  }
98
148
  catch (error) {
99
149
  const message = error instanceof Error ? error.message : String(error);
100
150
  results.push({ name: attachment.name, path: targetPath, error: message });
101
- onProgress?.(`Failed ${attachment.name}: ${message}`);
151
+ options.onProgress?.(`Failed ${attachment.name}: ${message}`);
102
152
  }
103
153
  }
104
154
  return results;
@@ -233,7 +283,11 @@ async function sendToOrchestrator(input, orch, messageId) {
233
283
  if (wsClient?.connected()) {
234
284
  wsClient.sendTyping(false);
235
285
  }
236
- return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
286
+ const message = error instanceof Error ? error.message : 'Unknown error';
287
+ if (/request timed out after \d+ms/i.test(message)) {
288
+ return 'Still working on it — I’ll respond as soon as I have results.';
289
+ }
290
+ return `Error: ${message}`;
237
291
  }
238
292
  finally {
239
293
  orch.setWorkerLogCallback(null);
@@ -270,14 +324,15 @@ export async function processMessage(message) {
270
324
  wsClient.sendProgress(message.id, msgText);
271
325
  }
272
326
  };
273
- const results = await saveAttachments(attachments, progress);
327
+ const baseText = stripAttachmentBlock(message.content);
328
+ const requestedSubdir = extractAttachmentSubdir(baseText);
329
+ const results = await saveAttachments(attachments, { subdir: requestedSubdir, onProgress: progress });
274
330
  const saved = results.filter((entry) => !entry.error);
275
331
  const failed = results.filter((entry) => entry.error);
276
332
  const savedLines = saved.map((entry) => `- ${humanizePath(entry.path)}`).join('\n');
277
333
  const failedLines = failed
278
334
  .map((entry) => `- ${entry.name}: ${entry.error}`)
279
335
  .join('\n');
280
- const baseText = stripAttachmentBlock(message.content);
281
336
  if (isSaveIntent(baseText)) {
282
337
  const responseLines = [
283
338
  saved.length > 0 ? `Saved ${saved.length} file${saved.length === 1 ? '' : 's'}:` : 'No files saved.',
@@ -22,7 +22,7 @@ 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';
25
+ const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-6';
26
26
  const DEFAULT_TOOL_MAX_TOKENS = 1024;
27
27
  function readBooleanEnv(value, fallback) {
28
28
  if (value === undefined)
@@ -161,6 +161,13 @@ YOUR HUB: ~/Lia-Hub/
161
161
  - **notebook-a/SCRATCHPAD.md** - Your working notes
162
162
  - **shared/outputs/** - Where workers save files
163
163
 
164
+ HUB PRIVACY:
165
+ - These hub files are INTERNAL. Do NOT list or reveal them to users.
166
+ - Never enumerate ~/Lia-Hub, CLAUDE.md, AGENTS.md, LANDMARKS.md, or other internal files.
167
+ - If a user asks to list files, direct them to /files or list channel files only.
168
+ - Home channels are private. Never access or list another user's Home channel.
169
+ - If asked about another user's Home channel, politely decline and offer to search shared channels.
170
+
164
171
  ASSET PREVIEWS:
165
172
  The Lia interface has a built-in gallery that shows worker outputs automatically.
166
173
  ⛔ NEVER use start_local_server or open_browser for previews
@@ -170,6 +177,7 @@ The Lia interface has a built-in gallery that shows worker outputs automatically
170
177
 
171
178
  FILES BROWSER:
172
179
  Users can type /files to open the Finder UI (cloud mode).
180
+ Treat /files as the canonical file surface (channel files). Do not expose hub internals.
173
181
  When asked to browse or locate files, point them to /files.
174
182
  Refer to /files instead of internal Lia-Hub paths in responses.
175
183
  When users ask you to create or update a file in /files, use files_write.
@@ -1597,7 +1605,7 @@ ${hubContextStr}
1597
1605
  let currentMessages = [...messages];
1598
1606
  let iterations = 0;
1599
1607
  const maxIterations = 30; // Increased for complex multi-tool tasks
1600
- const requestTimeoutMs = 60000;
1608
+ const requestTimeoutMs = 180000;
1601
1609
  const forcedToolChoice = this.getToolChoice(currentMessages);
1602
1610
  let retriedForcedTool = false;
1603
1611
  let retriedTodoCal = false;
@@ -48,7 +48,7 @@ function getFileType(ext) {
48
48
  return 'text';
49
49
  return 'other';
50
50
  }
51
- const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-5-20251101';
51
+ const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-6';
52
52
  function readBooleanEnv(value, fallback) {
53
53
  if (value === undefined)
54
54
  return fallback;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.58",
3
+ "version": "0.2.60",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",