@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 +65 -10
- package/dist/orchestrator.js +10 -2
- package/dist/workers/manager.js +1 -1
- package/package.json +1 -1
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,
|
|
78
|
-
const
|
|
79
|
-
const
|
|
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
|
-
|
|
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
|
|
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.',
|
package/dist/orchestrator.js
CHANGED
|
@@ -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-
|
|
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 =
|
|
1608
|
+
const requestTimeoutMs = 180000;
|
|
1601
1609
|
const forcedToolChoice = this.getToolChoice(currentMessages);
|
|
1602
1610
|
let retriedForcedTool = false;
|
|
1603
1611
|
let retriedTodoCal = false;
|
package/dist/workers/manager.js
CHANGED
|
@@ -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-
|
|
51
|
+
const DEFAULT_CLAUDE_MODEL = 'claude-opus-4-6';
|
|
52
52
|
function readBooleanEnv(value, fallback) {
|
|
53
53
|
if (value === undefined)
|
|
54
54
|
return fallback;
|