@siftd/connect-agent 0.2.59 → 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)
@@ -1605,7 +1605,7 @@ ${hubContextStr}
1605
1605
  let currentMessages = [...messages];
1606
1606
  let iterations = 0;
1607
1607
  const maxIterations = 30; // Increased for complex multi-tool tasks
1608
- const requestTimeoutMs = 60000;
1608
+ const requestTimeoutMs = 180000;
1609
1609
  const forcedToolChoice = this.getToolChoice(currentMessages);
1610
1610
  let retriedForcedTool = false;
1611
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.59",
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",