agentfeed 0.1.11 → 0.1.12
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/backends/claude.js +7 -1
- package/dist/backends/types.d.ts +1 -0
- package/dist/cli.js +10 -0
- package/dist/invoker.d.ts +2 -0
- package/dist/invoker.js +16 -5
- package/dist/mcp-server.js +68 -0
- package/dist/processor.js +19 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/backends/claude.js
CHANGED
|
@@ -24,7 +24,7 @@ export class ClaudeBackend {
|
|
|
24
24
|
fs.writeFileSync(this.mcpConfigPath, JSON.stringify(config, null, 2));
|
|
25
25
|
}
|
|
26
26
|
buildArgs(options) {
|
|
27
|
-
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools, model } = options;
|
|
27
|
+
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools, model, chrome } = options;
|
|
28
28
|
const args = [
|
|
29
29
|
"-p", prompt,
|
|
30
30
|
"--append-system-prompt", systemPrompt,
|
|
@@ -33,11 +33,17 @@ export class ClaudeBackend {
|
|
|
33
33
|
if (model) {
|
|
34
34
|
args.push("--model", model);
|
|
35
35
|
}
|
|
36
|
+
if (chrome) {
|
|
37
|
+
args.push("--chrome");
|
|
38
|
+
}
|
|
36
39
|
if (permissionMode === "yolo") {
|
|
37
40
|
args.push("--dangerously-skip-permissions");
|
|
38
41
|
}
|
|
39
42
|
else {
|
|
40
43
|
const allowedTools = ["mcp__agentfeed__*", ...(extraAllowedTools ?? [])];
|
|
44
|
+
if (chrome) {
|
|
45
|
+
allowedTools.push("mcp__claude-in-chrome__*");
|
|
46
|
+
}
|
|
41
47
|
for (const tool of allowedTools) {
|
|
42
48
|
args.push("--allowedTools", tool);
|
|
43
49
|
}
|
package/dist/backends/types.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -100,6 +100,16 @@ export function probeBackend(type) {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
export function confirmYolo() {
|
|
103
|
+
// --yes flag skips the interactive confirmation
|
|
104
|
+
if (process.argv.includes("--yes") || process.argv.includes("-y")) {
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log(" \x1b[33m⚠️ YOLO mode enabled. The agent can do literally anything.\x1b[0m");
|
|
107
|
+
console.log(" \x1b[33m No prompt sandboxing. No trust boundaries.\x1b[0m");
|
|
108
|
+
console.log(" \x1b[33m Prompt injection? Not your problem today.\x1b[0m");
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log(" Continue? (y/N): y (auto-confirmed via --yes)");
|
|
111
|
+
return Promise.resolve(true);
|
|
112
|
+
}
|
|
103
113
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
114
|
return new Promise((resolve) => {
|
|
105
115
|
console.log("");
|
package/dist/invoker.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface InvokeOptions {
|
|
|
9
9
|
permissionMode: PermissionMode;
|
|
10
10
|
extraAllowedTools?: string[];
|
|
11
11
|
model?: string;
|
|
12
|
+
chrome?: boolean;
|
|
12
13
|
sessionId?: string;
|
|
13
14
|
agentId?: string;
|
|
14
15
|
timeoutMs?: number;
|
|
@@ -16,5 +17,6 @@ export interface InvokeOptions {
|
|
|
16
17
|
export interface InvokeResult {
|
|
17
18
|
exitCode: number;
|
|
18
19
|
sessionId?: string;
|
|
20
|
+
timedOut: boolean;
|
|
19
21
|
}
|
|
20
22
|
export declare function invokeAgent(backend: CLIBackend, options: InvokeOptions): Promise<InvokeResult>;
|
package/dist/invoker.js
CHANGED
|
@@ -42,6 +42,7 @@ export function invokeAgent(backend, options) {
|
|
|
42
42
|
permissionMode: options.permissionMode,
|
|
43
43
|
extraAllowedTools: options.extraAllowedTools,
|
|
44
44
|
model: options.model,
|
|
45
|
+
chrome: options.chrome,
|
|
45
46
|
});
|
|
46
47
|
const env = backend.buildEnv({
|
|
47
48
|
...agentfeedEnv,
|
|
@@ -55,12 +56,14 @@ export function invokeAgent(backend, options) {
|
|
|
55
56
|
console.log(`Invoking ${backend.name}...`);
|
|
56
57
|
const child = spawn(backend.binaryName, args, {
|
|
57
58
|
env,
|
|
58
|
-
stdio: isNewSession ? ["
|
|
59
|
+
stdio: isNewSession ? ["ignore", "pipe", "inherit"] : ["ignore", "inherit", "inherit"],
|
|
59
60
|
});
|
|
60
61
|
// Timeout watchdog
|
|
61
62
|
let killTimer = null;
|
|
63
|
+
let timedOut = false;
|
|
62
64
|
if (options.timeoutMs) {
|
|
63
65
|
killTimer = setTimeout(() => {
|
|
66
|
+
timedOut = true;
|
|
64
67
|
console.warn(`Agent timed out after ${options.timeoutMs / 1000}s, killing process...`);
|
|
65
68
|
child.kill("SIGTERM");
|
|
66
69
|
setTimeout(() => { if (!child.killed)
|
|
@@ -104,7 +107,7 @@ export function invokeAgent(backend, options) {
|
|
|
104
107
|
if (isNewSession)
|
|
105
108
|
process.stdout.write("\n");
|
|
106
109
|
console.log(`Agent exited (code ${code ?? "unknown"})`);
|
|
107
|
-
resolve({ exitCode: code ?? 1, sessionId: sessionId ?? options.sessionId });
|
|
110
|
+
resolve({ exitCode: code ?? 1, sessionId: sessionId ?? options.sessionId, timedOut });
|
|
108
111
|
});
|
|
109
112
|
});
|
|
110
113
|
}
|
|
@@ -138,7 +141,15 @@ function buildSystemPrompt(options) {
|
|
|
138
141
|
- agentfeed_create_post - Create a new post in a feed
|
|
139
142
|
- agentfeed_get_comments - Get comments on a post (use since/author_type filters)
|
|
140
143
|
- agentfeed_post_comment - Post a comment (Korean and emoji supported!)
|
|
141
|
-
- agentfeed_download_file - Download and view uploaded files (images, etc.)
|
|
144
|
+
- agentfeed_download_file - Download and view uploaded files (images, etc.)
|
|
145
|
+
- agentfeed_upload_file - Upload a local file and get markdown URL${statusTool}`;
|
|
146
|
+
const chromeSection = options.chrome
|
|
147
|
+
? `\n\n# Chrome Browser
|
|
148
|
+
|
|
149
|
+
You have Chrome browser automation tools (mcp__claude-in-chrome__*) available.
|
|
150
|
+
Use these to navigate web pages, take screenshots, and interact with browser content.
|
|
151
|
+
To capture and share a web page: take a screenshot, save to /tmp, then upload via agentfeed_upload_file.`
|
|
152
|
+
: "";
|
|
142
153
|
const imageGuidance = `IMPORTANT: When content contains image URLs like , use agentfeed_download_file to view the image before responding about it.`;
|
|
143
154
|
if (options.permissionMode === "yolo") {
|
|
144
155
|
return `# AgentFeed
|
|
@@ -149,7 +160,7 @@ ${toolList}
|
|
|
149
160
|
|
|
150
161
|
Use these tools to interact with the feed. All content encoding is handled automatically.
|
|
151
162
|
|
|
152
|
-
${imageGuidance}`;
|
|
163
|
+
${imageGuidance}${chromeSection}`;
|
|
153
164
|
}
|
|
154
165
|
return `${SECURITY_POLICY}
|
|
155
166
|
|
|
@@ -161,7 +172,7 @@ ${toolList}
|
|
|
161
172
|
|
|
162
173
|
Use these tools to interact with the feed. All content encoding is handled automatically.
|
|
163
174
|
|
|
164
|
-
${imageGuidance}`;
|
|
175
|
+
${imageGuidance}${chromeSection}`;
|
|
165
176
|
}
|
|
166
177
|
function wrapUntrusted(text) {
|
|
167
178
|
return `<untrusted_content>\n${escapeXml(text)}\n</untrusted_content>`;
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
1
3
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -82,6 +84,22 @@ const TOOLS = [
|
|
|
82
84
|
required: ["url"],
|
|
83
85
|
},
|
|
84
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: "agentfeed_upload_file",
|
|
89
|
+
description: "Upload a file to AgentFeed. Returns the URL that can be used in markdown. " +
|
|
90
|
+
"For images: , for others: [filename](/api/uploads/up_xxx.ext). " +
|
|
91
|
+
"Use this to share generated charts, images, or any files in posts and comments.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
file_path: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Absolute path to the file to upload (e.g. /tmp/chart.svg)",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["file_path"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
85
103
|
{
|
|
86
104
|
name: "agentfeed_set_status",
|
|
87
105
|
description: "Report agent status (thinking/idle)",
|
|
@@ -184,6 +202,56 @@ async function handleToolCall(name, args, ctx) {
|
|
|
184
202
|
],
|
|
185
203
|
};
|
|
186
204
|
}
|
|
205
|
+
case "agentfeed_upload_file": {
|
|
206
|
+
const { file_path } = args;
|
|
207
|
+
const fileBuffer = await readFile(file_path);
|
|
208
|
+
const fileName = basename(file_path);
|
|
209
|
+
const ext = fileName.includes(".") ? fileName.split(".").pop().toLowerCase() : "";
|
|
210
|
+
const mimeTypes = {
|
|
211
|
+
svg: "image/svg+xml",
|
|
212
|
+
png: "image/png",
|
|
213
|
+
jpg: "image/jpeg",
|
|
214
|
+
jpeg: "image/jpeg",
|
|
215
|
+
gif: "image/gif",
|
|
216
|
+
webp: "image/webp",
|
|
217
|
+
pdf: "application/pdf",
|
|
218
|
+
json: "application/json",
|
|
219
|
+
csv: "text/csv",
|
|
220
|
+
txt: "text/plain",
|
|
221
|
+
md: "text/markdown",
|
|
222
|
+
html: "text/html",
|
|
223
|
+
mp4: "video/mp4",
|
|
224
|
+
webm: "video/webm",
|
|
225
|
+
};
|
|
226
|
+
const mimeType = mimeTypes[ext] || "application/octet-stream";
|
|
227
|
+
const blob = new Blob([fileBuffer], { type: mimeType });
|
|
228
|
+
const formData = new FormData();
|
|
229
|
+
formData.append("file", blob, fileName);
|
|
230
|
+
const uploadRes = await fetch(`${serverUrl}/api/uploads`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: {
|
|
233
|
+
Authorization: `Bearer ${client.apiKey}`,
|
|
234
|
+
Origin: serverUrl,
|
|
235
|
+
...(client.agentId ? { "X-Agent-Id": client.agentId } : {}),
|
|
236
|
+
},
|
|
237
|
+
body: formData,
|
|
238
|
+
});
|
|
239
|
+
if (!uploadRes.ok)
|
|
240
|
+
throw new Error(`Failed to upload file: ${uploadRes.status} ${await uploadRes.text()}`);
|
|
241
|
+
const upload = (await uploadRes.json());
|
|
242
|
+
const isImage = upload.mime_type.startsWith("image/");
|
|
243
|
+
const markdown = isImage
|
|
244
|
+
? ``
|
|
245
|
+
: `[${fileName}](${upload.url})`;
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: "text",
|
|
250
|
+
text: `File uploaded successfully.\nURL: ${upload.url}\nMarkdown: ${markdown}\nSize: ${upload.size} bytes`,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
187
255
|
case "agentfeed_set_status": {
|
|
188
256
|
const { status, feed_id, post_id } = args;
|
|
189
257
|
await client.setAgentStatus({ status, feed_id, post_id });
|
package/dist/processor.js
CHANGED
|
@@ -8,6 +8,8 @@ const MAX_BOT_MENTIONS_PER_POST = 4;
|
|
|
8
8
|
const wakeAttempts = new Map();
|
|
9
9
|
const botMentionCounts = new Map();
|
|
10
10
|
const runningKeys = new Set();
|
|
11
|
+
let retryTimer = null;
|
|
12
|
+
const RETRY_DELAY_MS = 3000;
|
|
11
13
|
// Periodic cleanup to prevent memory growth
|
|
12
14
|
setInterval(() => { wakeAttempts.clear(); botMentionCounts.clear(); }, 10 * 60 * 1000);
|
|
13
15
|
function triggerKey(t) {
|
|
@@ -34,6 +36,7 @@ function scheduleQueue(deps) {
|
|
|
34
36
|
if (queued.length === 0)
|
|
35
37
|
return;
|
|
36
38
|
const toRun = [];
|
|
39
|
+
let hasRequeued = false;
|
|
37
40
|
for (const t of queued) {
|
|
38
41
|
const attempts = wakeAttempts.get(t.eventId) ?? 0;
|
|
39
42
|
if (attempts >= MAX_WAKE_ATTEMPTS) {
|
|
@@ -44,12 +47,22 @@ function scheduleQueue(deps) {
|
|
|
44
47
|
if (runningKeys.size >= MAX_CONCURRENT || runningKeys.has(key)) {
|
|
45
48
|
// Same backend+session already running — re-queue
|
|
46
49
|
deps.queueStore.push(t);
|
|
50
|
+
hasRequeued = true;
|
|
47
51
|
}
|
|
48
52
|
else {
|
|
49
53
|
toRun.push(t);
|
|
50
54
|
runningKeys.add(key);
|
|
51
55
|
}
|
|
52
56
|
}
|
|
57
|
+
// Schedule retry for re-queued items so they don't wait for next SSE event
|
|
58
|
+
if (hasRequeued) {
|
|
59
|
+
if (retryTimer)
|
|
60
|
+
clearTimeout(retryTimer);
|
|
61
|
+
retryTimer = setTimeout(() => {
|
|
62
|
+
retryTimer = null;
|
|
63
|
+
scheduleQueue(deps);
|
|
64
|
+
}, RETRY_DELAY_MS);
|
|
65
|
+
}
|
|
53
66
|
for (const trigger of toRun) {
|
|
54
67
|
processItem(trigger, deps).catch((err) => {
|
|
55
68
|
console.error(`Error processing ${trigger.postId}:`, err);
|
|
@@ -86,6 +99,7 @@ async function processItem(trigger, deps) {
|
|
|
86
99
|
? ba.config.allowed_tools
|
|
87
100
|
: deps.extraAllowedTools;
|
|
88
101
|
const effectiveModel = ba.config?.model;
|
|
102
|
+
const effectiveChrome = ba.config?.chrome ?? false;
|
|
89
103
|
let retries = 0;
|
|
90
104
|
let success = false;
|
|
91
105
|
try {
|
|
@@ -100,6 +114,7 @@ async function processItem(trigger, deps) {
|
|
|
100
114
|
permissionMode: effectivePermission,
|
|
101
115
|
extraAllowedTools: effectiveTools,
|
|
102
116
|
model: effectiveModel ?? undefined,
|
|
117
|
+
chrome: effectiveChrome,
|
|
103
118
|
sessionId: ba.sessionStore.get(trigger.sessionName),
|
|
104
119
|
agentId: sessionAgentId,
|
|
105
120
|
timeoutMs: AGENT_TIMEOUT_MS,
|
|
@@ -115,6 +130,10 @@ async function processItem(trigger, deps) {
|
|
|
115
130
|
success = true;
|
|
116
131
|
break;
|
|
117
132
|
}
|
|
133
|
+
if (result.timedOut) {
|
|
134
|
+
console.warn("Agent timed out, skipping retry");
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
118
137
|
if (result.exitCode !== 0 && ba.sessionStore.get(trigger.sessionName)) {
|
|
119
138
|
console.log("Session may be stale, clearing and retrying as new session...");
|
|
120
139
|
ba.sessionStore.delete(trigger.sessionName);
|
package/dist/types.d.ts
CHANGED