agentfeed 0.1.10 → 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 +10 -1
- package/dist/backends/codex.js +4 -1
- package/dist/backends/gemini.js +4 -1
- package/dist/backends/types.d.ts +2 -0
- package/dist/cli.js +10 -0
- package/dist/index.js +2 -1
- package/dist/invoker.d.ts +3 -0
- package/dist/invoker.js +17 -5
- package/dist/mcp-server.js +68 -0
- package/dist/processor.js +21 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/backends/claude.js
CHANGED
|
@@ -24,17 +24,26 @@ 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 } = 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,
|
|
31
31
|
"--mcp-config", this.mcpConfigPath,
|
|
32
32
|
];
|
|
33
|
+
if (model) {
|
|
34
|
+
args.push("--model", model);
|
|
35
|
+
}
|
|
36
|
+
if (chrome) {
|
|
37
|
+
args.push("--chrome");
|
|
38
|
+
}
|
|
33
39
|
if (permissionMode === "yolo") {
|
|
34
40
|
args.push("--dangerously-skip-permissions");
|
|
35
41
|
}
|
|
36
42
|
else {
|
|
37
43
|
const allowedTools = ["mcp__agentfeed__*", ...(extraAllowedTools ?? [])];
|
|
44
|
+
if (chrome) {
|
|
45
|
+
allowedTools.push("mcp__claude-in-chrome__*");
|
|
46
|
+
}
|
|
38
47
|
for (const tool of allowedTools) {
|
|
39
48
|
args.push("--allowedTools", tool);
|
|
40
49
|
}
|
package/dist/backends/codex.js
CHANGED
|
@@ -10,8 +10,11 @@ export class CodexBackend {
|
|
|
10
10
|
this.mcpEnv = env;
|
|
11
11
|
}
|
|
12
12
|
buildArgs(options) {
|
|
13
|
-
const { prompt, systemPrompt, sessionId, permissionMode } = options;
|
|
13
|
+
const { prompt, systemPrompt, sessionId, permissionMode, model } = options;
|
|
14
14
|
const args = ["exec"];
|
|
15
|
+
if (model) {
|
|
16
|
+
args.push("-m", model);
|
|
17
|
+
}
|
|
15
18
|
// MCP config via dot-notation -c flags (codex-cli 0.46+ requires struct, not JSON string)
|
|
16
19
|
const prefix = "mcp_servers.agentfeed";
|
|
17
20
|
args.push("-c", `${prefix}.command=${this.mcpCommand}`);
|
package/dist/backends/gemini.js
CHANGED
|
@@ -33,10 +33,13 @@ export class GeminiBackend {
|
|
|
33
33
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
34
34
|
}
|
|
35
35
|
buildArgs(options) {
|
|
36
|
-
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools } = options;
|
|
36
|
+
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools, model } = options;
|
|
37
37
|
// Gemini has no --append-system-prompt flag, embed in user prompt
|
|
38
38
|
const fullPrompt = `[System Instructions]\n${systemPrompt}\n\n[Task]\n${prompt}`;
|
|
39
39
|
const args = [fullPrompt];
|
|
40
|
+
if (model) {
|
|
41
|
+
args.push("--model", model);
|
|
42
|
+
}
|
|
40
43
|
if (sessionId) {
|
|
41
44
|
args.push("--resume", sessionId);
|
|
42
45
|
}
|
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/index.js
CHANGED
|
@@ -121,7 +121,8 @@ async function main() {
|
|
|
121
121
|
try {
|
|
122
122
|
const config = await client.getAgentConfig(agent.id);
|
|
123
123
|
ba.config = config;
|
|
124
|
-
|
|
124
|
+
const modelLabel = config.model ? `, model: ${config.model}` : "";
|
|
125
|
+
console.log(`Agent: ${agent.name} (${agent.id}) [${type}] (server config: ${config.permission_mode}, tools: ${config.allowed_tools.length}${modelLabel})`);
|
|
125
126
|
}
|
|
126
127
|
catch {
|
|
127
128
|
console.log(`Agent: ${agent.name} (${agent.id}) [${type}] (using CLI defaults)`);
|
package/dist/invoker.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface InvokeOptions {
|
|
|
8
8
|
recentContext: string;
|
|
9
9
|
permissionMode: PermissionMode;
|
|
10
10
|
extraAllowedTools?: string[];
|
|
11
|
+
model?: string;
|
|
12
|
+
chrome?: boolean;
|
|
11
13
|
sessionId?: string;
|
|
12
14
|
agentId?: string;
|
|
13
15
|
timeoutMs?: number;
|
|
@@ -15,5 +17,6 @@ export interface InvokeOptions {
|
|
|
15
17
|
export interface InvokeResult {
|
|
16
18
|
exitCode: number;
|
|
17
19
|
sessionId?: string;
|
|
20
|
+
timedOut: boolean;
|
|
18
21
|
}
|
|
19
22
|
export declare function invokeAgent(backend: CLIBackend, options: InvokeOptions): Promise<InvokeResult>;
|
package/dist/invoker.js
CHANGED
|
@@ -41,6 +41,8 @@ export function invokeAgent(backend, options) {
|
|
|
41
41
|
sessionId: options.sessionId,
|
|
42
42
|
permissionMode: options.permissionMode,
|
|
43
43
|
extraAllowedTools: options.extraAllowedTools,
|
|
44
|
+
model: options.model,
|
|
45
|
+
chrome: options.chrome,
|
|
44
46
|
});
|
|
45
47
|
const env = backend.buildEnv({
|
|
46
48
|
...agentfeedEnv,
|
|
@@ -54,12 +56,14 @@ export function invokeAgent(backend, options) {
|
|
|
54
56
|
console.log(`Invoking ${backend.name}...`);
|
|
55
57
|
const child = spawn(backend.binaryName, args, {
|
|
56
58
|
env,
|
|
57
|
-
stdio: isNewSession ? ["
|
|
59
|
+
stdio: isNewSession ? ["ignore", "pipe", "inherit"] : ["ignore", "inherit", "inherit"],
|
|
58
60
|
});
|
|
59
61
|
// Timeout watchdog
|
|
60
62
|
let killTimer = null;
|
|
63
|
+
let timedOut = false;
|
|
61
64
|
if (options.timeoutMs) {
|
|
62
65
|
killTimer = setTimeout(() => {
|
|
66
|
+
timedOut = true;
|
|
63
67
|
console.warn(`Agent timed out after ${options.timeoutMs / 1000}s, killing process...`);
|
|
64
68
|
child.kill("SIGTERM");
|
|
65
69
|
setTimeout(() => { if (!child.killed)
|
|
@@ -103,7 +107,7 @@ export function invokeAgent(backend, options) {
|
|
|
103
107
|
if (isNewSession)
|
|
104
108
|
process.stdout.write("\n");
|
|
105
109
|
console.log(`Agent exited (code ${code ?? "unknown"})`);
|
|
106
|
-
resolve({ exitCode: code ?? 1, sessionId: sessionId ?? options.sessionId });
|
|
110
|
+
resolve({ exitCode: code ?? 1, sessionId: sessionId ?? options.sessionId, timedOut });
|
|
107
111
|
});
|
|
108
112
|
});
|
|
109
113
|
}
|
|
@@ -137,7 +141,15 @@ function buildSystemPrompt(options) {
|
|
|
137
141
|
- agentfeed_create_post - Create a new post in a feed
|
|
138
142
|
- agentfeed_get_comments - Get comments on a post (use since/author_type filters)
|
|
139
143
|
- agentfeed_post_comment - Post a comment (Korean and emoji supported!)
|
|
140
|
-
- 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
|
+
: "";
|
|
141
153
|
const imageGuidance = `IMPORTANT: When content contains image URLs like , use agentfeed_download_file to view the image before responding about it.`;
|
|
142
154
|
if (options.permissionMode === "yolo") {
|
|
143
155
|
return `# AgentFeed
|
|
@@ -148,7 +160,7 @@ ${toolList}
|
|
|
148
160
|
|
|
149
161
|
Use these tools to interact with the feed. All content encoding is handled automatically.
|
|
150
162
|
|
|
151
|
-
${imageGuidance}`;
|
|
163
|
+
${imageGuidance}${chromeSection}`;
|
|
152
164
|
}
|
|
153
165
|
return `${SECURITY_POLICY}
|
|
154
166
|
|
|
@@ -160,7 +172,7 @@ ${toolList}
|
|
|
160
172
|
|
|
161
173
|
Use these tools to interact with the feed. All content encoding is handled automatically.
|
|
162
174
|
|
|
163
|
-
${imageGuidance}`;
|
|
175
|
+
${imageGuidance}${chromeSection}`;
|
|
164
176
|
}
|
|
165
177
|
function wrapUntrusted(text) {
|
|
166
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);
|
|
@@ -85,6 +98,8 @@ async function processItem(trigger, deps) {
|
|
|
85
98
|
const effectiveTools = ba.config?.allowed_tools?.length
|
|
86
99
|
? ba.config.allowed_tools
|
|
87
100
|
: deps.extraAllowedTools;
|
|
101
|
+
const effectiveModel = ba.config?.model;
|
|
102
|
+
const effectiveChrome = ba.config?.chrome ?? false;
|
|
88
103
|
let retries = 0;
|
|
89
104
|
let success = false;
|
|
90
105
|
try {
|
|
@@ -98,6 +113,8 @@ async function processItem(trigger, deps) {
|
|
|
98
113
|
recentContext,
|
|
99
114
|
permissionMode: effectivePermission,
|
|
100
115
|
extraAllowedTools: effectiveTools,
|
|
116
|
+
model: effectiveModel ?? undefined,
|
|
117
|
+
chrome: effectiveChrome,
|
|
101
118
|
sessionId: ba.sessionStore.get(trigger.sessionName),
|
|
102
119
|
agentId: sessionAgentId,
|
|
103
120
|
timeoutMs: AGENT_TIMEOUT_MS,
|
|
@@ -113,6 +130,10 @@ async function processItem(trigger, deps) {
|
|
|
113
130
|
success = true;
|
|
114
131
|
break;
|
|
115
132
|
}
|
|
133
|
+
if (result.timedOut) {
|
|
134
|
+
console.warn("Agent timed out, skipping retry");
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
116
137
|
if (result.exitCode !== 0 && ba.sessionStore.get(trigger.sessionName)) {
|
|
117
138
|
console.log("Session may be stale, clearing and retrying as new session...");
|
|
118
139
|
ba.sessionStore.delete(trigger.sessionName);
|
package/dist/types.d.ts
CHANGED