agentfeed 0.1.6 → 0.1.8
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/bin/mcp-server.js +21 -0
- package/dist/api-client.d.ts +15 -4
- package/dist/api-client.js +54 -4
- package/dist/follow-store.d.ts +10 -0
- package/dist/follow-store.js +29 -0
- package/dist/index.js +192 -67
- package/dist/invoker.d.ts +4 -2
- package/dist/invoker.js +119 -11
- package/dist/mcp-config.d.ts +11 -0
- package/dist/mcp-config.js +25 -0
- package/dist/mcp-server.d.ts +8 -0
- package/dist/mcp-server.js +193 -0
- package/dist/persistent-store.d.ts +8 -0
- package/dist/persistent-store.js +28 -0
- package/dist/post-session-store.d.ts +10 -0
- package/dist/post-session-store.js +35 -0
- package/dist/queue-store.d.ts +11 -0
- package/dist/queue-store.js +32 -0
- package/dist/scanner.d.ts +3 -1
- package/dist/scanner.js +80 -26
- package/dist/session-store.d.ts +4 -4
- package/dist/session-store.js +12 -29
- package/dist/sse-client.d.ts +1 -1
- package/dist/sse-client.js +88 -25
- package/dist/trigger.d.ts +3 -1
- package/dist/trigger.js +31 -19
- package/dist/types.d.ts +19 -6
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +15 -0
- package/package.json +3 -2
package/dist/scanner.js
CHANGED
|
@@ -1,50 +1,104 @@
|
|
|
1
|
-
|
|
1
|
+
import { parseMention, isBotAuthor } from "./utils.js";
|
|
2
|
+
export async function scanUnprocessed(client, agent, followStore, postSessionStore) {
|
|
2
3
|
const triggers = [];
|
|
3
4
|
const feeds = await client.getFeeds();
|
|
5
|
+
const processedPostIds = new Set();
|
|
4
6
|
for (const feed of feeds) {
|
|
7
|
+
// --- Scan comments (existing logic) ---
|
|
5
8
|
const comments = await client.getFeedComments(feed.id, {
|
|
6
9
|
author_type: "human",
|
|
7
10
|
limit: 50,
|
|
8
11
|
});
|
|
12
|
+
// Group comments by post to find the last human comment per post
|
|
13
|
+
const byPost = new Map();
|
|
9
14
|
for (const comment of comments.data) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const list = byPost.get(comment.post_id) ?? [];
|
|
16
|
+
list.push(comment);
|
|
17
|
+
byPost.set(comment.post_id, list);
|
|
18
|
+
}
|
|
19
|
+
for (const [postId, postComments] of byPost) {
|
|
20
|
+
// Determine the best trigger type for this post
|
|
21
|
+
let bestTriggerType = null;
|
|
22
|
+
let bestComment = null;
|
|
23
|
+
let bestSessionName = "default";
|
|
24
|
+
for (const comment of postComments) {
|
|
25
|
+
// Check for @mentions (highest priority)
|
|
26
|
+
const mention = parseMention(comment.content, agent.name);
|
|
27
|
+
if (mention.mentioned) {
|
|
28
|
+
bestTriggerType = "mention";
|
|
29
|
+
bestComment = comment;
|
|
30
|
+
bestSessionName = mention.sessionName;
|
|
31
|
+
}
|
|
32
|
+
// Check if this is on an agent-owned post
|
|
33
|
+
if (!bestTriggerType && comment.post_created_by === agent.id) {
|
|
34
|
+
bestTriggerType = "own_post_comment";
|
|
35
|
+
bestComment = comment;
|
|
36
|
+
bestSessionName = postSessionStore?.get(postId) ?? "default";
|
|
37
|
+
}
|
|
16
38
|
}
|
|
17
|
-
// Check
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
// Check if this is in a followed thread
|
|
40
|
+
if (!bestTriggerType && followStore?.has(postId)) {
|
|
41
|
+
bestTriggerType = "thread_follow_up";
|
|
42
|
+
// Use the last human comment as the trigger
|
|
43
|
+
bestComment = postComments[postComments.length - 1] ?? null;
|
|
44
|
+
bestSessionName = postSessionStore?.get(postId) ?? "default";
|
|
21
45
|
}
|
|
22
|
-
if (!
|
|
46
|
+
if (!bestTriggerType || !bestComment)
|
|
23
47
|
continue;
|
|
24
|
-
// Check if there's
|
|
25
|
-
const
|
|
26
|
-
|
|
48
|
+
// Check if there's a bot reply after the LAST human comment in this post
|
|
49
|
+
const lastHumanComment = postComments[postComments.length - 1];
|
|
50
|
+
const replies = await client.getPostComments(postId, {
|
|
51
|
+
since: lastHumanComment.created_at,
|
|
27
52
|
author_type: "bot",
|
|
28
53
|
limit: 1,
|
|
29
54
|
});
|
|
30
55
|
if (replies.data.length === 0) {
|
|
31
56
|
triggers.push({
|
|
32
|
-
triggerType,
|
|
33
|
-
eventId:
|
|
57
|
+
triggerType: bestTriggerType,
|
|
58
|
+
eventId: bestComment.id,
|
|
34
59
|
feedId: feed.id,
|
|
35
60
|
feedName: feed.name,
|
|
36
|
-
postId
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
postId,
|
|
62
|
+
content: bestComment.content,
|
|
63
|
+
authorName: bestComment.author_name,
|
|
64
|
+
sessionName: bestSessionName,
|
|
65
|
+
});
|
|
66
|
+
processedPostIds.add(postId);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// --- Scan post bodies for @mentions ---
|
|
70
|
+
const posts = await client.getFeedPosts(feed.id, { limit: 50 });
|
|
71
|
+
for (const post of posts.data) {
|
|
72
|
+
// Skip posts already handled by comment scan
|
|
73
|
+
if (processedPostIds.has(post.id))
|
|
74
|
+
continue;
|
|
75
|
+
// Skip bot-authored posts
|
|
76
|
+
if (isBotAuthor(post.created_by))
|
|
77
|
+
continue;
|
|
78
|
+
// Skip posts without content
|
|
79
|
+
if (!post.content)
|
|
80
|
+
continue;
|
|
81
|
+
const mention = parseMention(post.content, agent.name);
|
|
82
|
+
if (!mention.mentioned)
|
|
83
|
+
continue;
|
|
84
|
+
// Check if there's any bot reply on this post
|
|
85
|
+
const botReplies = await client.getPostComments(post.id, {
|
|
86
|
+
author_type: "bot",
|
|
87
|
+
limit: 1,
|
|
88
|
+
});
|
|
89
|
+
if (botReplies.data.length === 0) {
|
|
90
|
+
triggers.push({
|
|
91
|
+
triggerType: "mention",
|
|
92
|
+
eventId: post.id,
|
|
93
|
+
feedId: feed.id,
|
|
94
|
+
feedName: feed.name,
|
|
95
|
+
postId: post.id,
|
|
96
|
+
content: post.content,
|
|
97
|
+
authorName: post.author_name,
|
|
98
|
+
sessionName: mention.sessionName,
|
|
40
99
|
});
|
|
41
100
|
}
|
|
42
101
|
}
|
|
43
102
|
}
|
|
44
103
|
return triggers;
|
|
45
104
|
}
|
|
46
|
-
function containsMention(text, agentName) {
|
|
47
|
-
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
-
const regex = new RegExp(`@${escaped}\\b`, "i");
|
|
49
|
-
return regex.test(text);
|
|
50
|
-
}
|
package/dist/session-store.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export declare class SessionStore extends PersistentStore {
|
|
2
3
|
private map;
|
|
3
|
-
private filePath;
|
|
4
4
|
constructor(filePath?: string);
|
|
5
|
+
protected serialize(): string;
|
|
6
|
+
protected deserialize(raw: string): void;
|
|
5
7
|
get(postId: string): string | undefined;
|
|
6
8
|
set(postId: string, sessionId: string): void;
|
|
7
9
|
delete(postId: string): void;
|
|
8
|
-
private load;
|
|
9
|
-
private save;
|
|
10
10
|
}
|
package/dist/session-store.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
const DEFAULT_DIR = join(homedir(), ".agentfeed");
|
|
5
|
-
export class SessionStore {
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export class SessionStore extends PersistentStore {
|
|
6
3
|
map = new Map();
|
|
7
|
-
filePath;
|
|
8
4
|
constructor(filePath) {
|
|
9
|
-
|
|
5
|
+
super("sessions.json", filePath);
|
|
10
6
|
this.load();
|
|
11
7
|
}
|
|
8
|
+
serialize() {
|
|
9
|
+
return JSON.stringify(Object.fromEntries(this.map), null, 2);
|
|
10
|
+
}
|
|
11
|
+
deserialize(raw) {
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
for (const [k, v] of Object.entries(data)) {
|
|
14
|
+
this.map.set(k, v);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
12
17
|
get(postId) {
|
|
13
18
|
return this.map.get(postId);
|
|
14
19
|
}
|
|
@@ -20,26 +25,4 @@ export class SessionStore {
|
|
|
20
25
|
this.map.delete(postId);
|
|
21
26
|
this.save();
|
|
22
27
|
}
|
|
23
|
-
load() {
|
|
24
|
-
try {
|
|
25
|
-
const raw = readFileSync(this.filePath, "utf-8");
|
|
26
|
-
const data = JSON.parse(raw);
|
|
27
|
-
for (const [k, v] of Object.entries(data)) {
|
|
28
|
-
this.map.set(k, v);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// File doesn't exist or invalid JSON — start fresh
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
save() {
|
|
36
|
-
try {
|
|
37
|
-
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
38
|
-
const data = Object.fromEntries(this.map);
|
|
39
|
-
writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
console.error("Failed to save sessions:", err);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
28
|
}
|
package/dist/sse-client.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export interface SSEEvent {
|
|
|
6
6
|
export interface SSEConnection {
|
|
7
7
|
close: () => void;
|
|
8
8
|
}
|
|
9
|
-
export declare function connectSSE(url: string, apiKey: string, onEvent: (event: SSEEvent) => void, onError: (error: Error) => void): SSEConnection;
|
|
9
|
+
export declare function connectSSE(url: string, apiKey: string, agentId: string | undefined, onEvent: (event: SSEEvent) => void, onError: (error: Error) => void): SSEConnection;
|
package/dist/sse-client.js
CHANGED
|
@@ -1,31 +1,94 @@
|
|
|
1
1
|
import { EventSource } from "eventsource";
|
|
2
|
-
const EVENT_TYPES = ["heartbeat", "post_created", "comment_created"];
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
2
|
+
const EVENT_TYPES = ["heartbeat", "post_created", "comment_created", "session_deleted"];
|
|
3
|
+
const BACKOFF_INITIAL_MS = 1_000;
|
|
4
|
+
const BACKOFF_MAX_MS = 60_000;
|
|
5
|
+
const BACKOFF_RESET_AFTER_MS = 30_000;
|
|
6
|
+
export function connectSSE(url, apiKey, agentId, onEvent, onError) {
|
|
7
|
+
let closed = false;
|
|
8
|
+
let currentEs = null;
|
|
9
|
+
let backoffMs = BACKOFF_INITIAL_MS;
|
|
10
|
+
let reconnectTimer = null;
|
|
11
|
+
let lastConnectedAt = 0;
|
|
12
|
+
let processedEventIds = new Set();
|
|
13
|
+
let eventIdCleanupTimer = null;
|
|
14
|
+
function connect() {
|
|
15
|
+
if (closed)
|
|
16
|
+
return;
|
|
17
|
+
const es = new EventSource(url, {
|
|
18
|
+
fetch: (input, init) => fetch(input, {
|
|
19
|
+
...init,
|
|
20
|
+
headers: {
|
|
21
|
+
...init.headers,
|
|
22
|
+
Authorization: `Bearer ${apiKey}`,
|
|
23
|
+
...(agentId ? { "X-Agent-Id": agentId } : {}),
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
26
|
});
|
|
27
|
+
currentEs = es;
|
|
28
|
+
es.onopen = () => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const isFirstConnect = lastConnectedAt === 0;
|
|
31
|
+
// Only reset backoff if previous connection was stable (lasted > 30s)
|
|
32
|
+
if (!isFirstConnect && now - lastConnectedAt > BACKOFF_RESET_AFTER_MS) {
|
|
33
|
+
backoffMs = BACKOFF_INITIAL_MS;
|
|
34
|
+
}
|
|
35
|
+
if (isFirstConnect) {
|
|
36
|
+
console.log("SSE connected.");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log("SSE reconnected.");
|
|
40
|
+
}
|
|
41
|
+
lastConnectedAt = now;
|
|
42
|
+
};
|
|
43
|
+
es.onerror = (err) => {
|
|
44
|
+
// EventSource auto-reconnects on its own, but we want manual control
|
|
45
|
+
// Close the current one and reconnect with backoff
|
|
46
|
+
es.close();
|
|
47
|
+
currentEs = null;
|
|
48
|
+
if (closed)
|
|
49
|
+
return;
|
|
50
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
51
|
+
onError(new Error(err.message ?? "SSE connection closed"));
|
|
52
|
+
}
|
|
53
|
+
console.log(`SSE disconnected. Reconnecting in ${backoffMs / 1000}s...`);
|
|
54
|
+
reconnectTimer = setTimeout(() => {
|
|
55
|
+
reconnectTimer = null;
|
|
56
|
+
connect();
|
|
57
|
+
}, backoffMs);
|
|
58
|
+
// Exponential backoff: 1s -> 2s -> 4s -> 8s -> ... -> 60s
|
|
59
|
+
backoffMs = Math.min(backoffMs * 2, BACKOFF_MAX_MS);
|
|
60
|
+
};
|
|
61
|
+
for (const eventType of EVENT_TYPES) {
|
|
62
|
+
es.addEventListener(eventType, (e) => {
|
|
63
|
+
// Deduplicate events by ID
|
|
64
|
+
const eventId = e.lastEventId || undefined;
|
|
65
|
+
if (eventId) {
|
|
66
|
+
if (processedEventIds.has(eventId))
|
|
67
|
+
return;
|
|
68
|
+
processedEventIds.add(eventId);
|
|
69
|
+
}
|
|
70
|
+
onEvent({
|
|
71
|
+
type: eventType,
|
|
72
|
+
data: e.data,
|
|
73
|
+
id: eventId,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
27
77
|
}
|
|
78
|
+
// Periodically clean old event IDs to prevent memory growth
|
|
79
|
+
eventIdCleanupTimer = setInterval(() => {
|
|
80
|
+
processedEventIds = new Set();
|
|
81
|
+
}, 5 * 60 * 1000);
|
|
82
|
+
connect();
|
|
28
83
|
return {
|
|
29
|
-
close: () =>
|
|
84
|
+
close: () => {
|
|
85
|
+
closed = true;
|
|
86
|
+
if (reconnectTimer)
|
|
87
|
+
clearTimeout(reconnectTimer);
|
|
88
|
+
if (eventIdCleanupTimer)
|
|
89
|
+
clearInterval(eventIdCleanupTimer);
|
|
90
|
+
currentEs?.close();
|
|
91
|
+
currentEs = null;
|
|
92
|
+
},
|
|
30
93
|
};
|
|
31
94
|
}
|
package/dist/trigger.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { FollowStore } from "./follow-store.js";
|
|
2
|
+
import type { PostSessionStore } from "./post-session-store.js";
|
|
1
3
|
import type { GlobalEvent, TriggerContext, AgentInfo } from "./types.js";
|
|
2
|
-
export declare function detectTrigger(event: GlobalEvent, agent: AgentInfo): TriggerContext | null;
|
|
4
|
+
export declare function detectTrigger(event: GlobalEvent, agent: AgentInfo, followStore?: FollowStore, postSessionStore?: PostSessionStore): TriggerContext | null;
|
package/dist/trigger.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { parseMention } from "./utils.js";
|
|
2
|
+
export function detectTrigger(event, agent, followStore, postSessionStore) {
|
|
2
3
|
if (event.type === "comment_created") {
|
|
3
4
|
// Trigger 1: Comment on own post
|
|
4
5
|
if (event.post_created_by === agent.id) {
|
|
@@ -8,45 +9,56 @@ export function detectTrigger(event, agent) {
|
|
|
8
9
|
feedId: event.feed_id,
|
|
9
10
|
feedName: "",
|
|
10
11
|
postId: event.post_id,
|
|
11
|
-
postTitle: event.post_title,
|
|
12
12
|
content: event.content,
|
|
13
13
|
authorName: event.author_name,
|
|
14
|
+
sessionName: postSessionStore?.get(event.post_id) ?? "default",
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
// Trigger 2: @mention in comment
|
|
17
|
-
|
|
18
|
+
const mention = parseMention(event.content, agent.name);
|
|
19
|
+
if (mention.mentioned) {
|
|
18
20
|
return {
|
|
19
21
|
triggerType: "mention",
|
|
20
22
|
eventId: event.id,
|
|
21
23
|
feedId: event.feed_id,
|
|
22
24
|
feedName: "",
|
|
23
25
|
postId: event.post_id,
|
|
24
|
-
postTitle: event.post_title,
|
|
25
26
|
content: event.content,
|
|
26
27
|
authorName: event.author_name,
|
|
28
|
+
sessionName: mention.sessionName,
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Trigger 2: @mention in post title or content
|
|
32
|
-
const text = [event.title, event.content].filter(Boolean).join(" ");
|
|
33
|
-
if (containsMention(text, agent.name)) {
|
|
31
|
+
// Trigger 3: Comment in a followed thread
|
|
32
|
+
if (followStore?.has(event.post_id)) {
|
|
34
33
|
return {
|
|
35
|
-
triggerType: "
|
|
34
|
+
triggerType: "thread_follow_up",
|
|
36
35
|
eventId: event.id,
|
|
37
36
|
feedId: event.feed_id,
|
|
38
|
-
feedName:
|
|
39
|
-
postId: event.
|
|
40
|
-
|
|
41
|
-
content: event.content ?? "",
|
|
37
|
+
feedName: "",
|
|
38
|
+
postId: event.post_id,
|
|
39
|
+
content: event.content,
|
|
42
40
|
authorName: event.author_name,
|
|
41
|
+
sessionName: postSessionStore?.get(event.post_id) ?? "default",
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
}
|
|
45
|
+
if (event.type === "post_created") {
|
|
46
|
+
// @mention in post content
|
|
47
|
+
if (event.content) {
|
|
48
|
+
const mention = parseMention(event.content, agent.name);
|
|
49
|
+
if (mention.mentioned) {
|
|
50
|
+
return {
|
|
51
|
+
triggerType: "mention",
|
|
52
|
+
eventId: event.id,
|
|
53
|
+
feedId: event.feed_id,
|
|
54
|
+
feedName: event.feed_name,
|
|
55
|
+
postId: event.id,
|
|
56
|
+
content: event.content,
|
|
57
|
+
authorName: event.author_name,
|
|
58
|
+
sessionName: mention.sessionName,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
return null;
|
|
47
64
|
}
|
|
48
|
-
function containsMention(text, agentName) {
|
|
49
|
-
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
-
const regex = new RegExp(`@${escaped}\\b`, "i");
|
|
51
|
-
return regex.test(text);
|
|
52
|
-
}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export interface GlobalPostEvent {
|
|
|
8
8
|
id: string;
|
|
9
9
|
feed_id: string;
|
|
10
10
|
feed_name: string;
|
|
11
|
-
title: string | null;
|
|
12
11
|
content: string | null;
|
|
13
12
|
created_by: string | null;
|
|
14
13
|
author_name: string | null;
|
|
@@ -24,24 +23,38 @@ export interface GlobalCommentEvent {
|
|
|
24
23
|
created_by: string | null;
|
|
25
24
|
author_name: string | null;
|
|
26
25
|
created_at: string;
|
|
27
|
-
post_title: string | null;
|
|
28
26
|
post_created_by: string | null;
|
|
29
27
|
}
|
|
30
|
-
export
|
|
28
|
+
export interface GlobalSessionDeletedEvent {
|
|
29
|
+
type: "session_deleted";
|
|
30
|
+
agent_id: string;
|
|
31
|
+
agent_name: string;
|
|
32
|
+
session_name: string;
|
|
33
|
+
}
|
|
34
|
+
export type GlobalEvent = GlobalPostEvent | GlobalCommentEvent | GlobalSessionDeletedEvent;
|
|
31
35
|
export interface TriggerContext {
|
|
32
|
-
triggerType: "own_post_comment" | "mention";
|
|
36
|
+
triggerType: "own_post_comment" | "mention" | "thread_follow_up";
|
|
33
37
|
eventId: string;
|
|
34
38
|
feedId: string;
|
|
35
39
|
feedName: string;
|
|
36
40
|
postId: string;
|
|
37
|
-
postTitle: string | null;
|
|
38
41
|
content: string;
|
|
39
42
|
authorName: string | null;
|
|
43
|
+
sessionName: string;
|
|
40
44
|
}
|
|
41
45
|
export interface FeedItem {
|
|
42
46
|
id: string;
|
|
43
47
|
name: string;
|
|
44
48
|
}
|
|
49
|
+
export interface PostItem {
|
|
50
|
+
id: string;
|
|
51
|
+
feed_id: string;
|
|
52
|
+
content: string | null;
|
|
53
|
+
created_by: string;
|
|
54
|
+
author_name: string | null;
|
|
55
|
+
created_at: string;
|
|
56
|
+
comment_count: number;
|
|
57
|
+
}
|
|
45
58
|
export interface FeedCommentItem {
|
|
46
59
|
id: string;
|
|
47
60
|
post_id: string;
|
|
@@ -50,7 +63,6 @@ export interface FeedCommentItem {
|
|
|
50
63
|
created_by: string | null;
|
|
51
64
|
author_name: string | null;
|
|
52
65
|
created_at: string;
|
|
53
|
-
post_title: string | null;
|
|
54
66
|
post_created_by: string | null;
|
|
55
67
|
}
|
|
56
68
|
export interface CommentItem {
|
|
@@ -67,3 +79,4 @@ export interface PaginatedResponse<T> {
|
|
|
67
79
|
next_cursor: string | null;
|
|
68
80
|
has_more: boolean;
|
|
69
81
|
}
|
|
82
|
+
export type PermissionMode = "safe" | "yolo";
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface MentionMatch {
|
|
2
|
+
mentioned: boolean;
|
|
3
|
+
sessionName: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseMention(text: string, agentName: string): MentionMatch;
|
|
6
|
+
export declare function containsMention(text: string, agentName: string): boolean;
|
|
7
|
+
/** Check if created_by belongs to a bot (API key or registered agent) */
|
|
8
|
+
export declare function isBotAuthor(createdBy: string | null | undefined): boolean;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function parseMention(text, agentName) {
|
|
2
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3
|
+
const regex = new RegExp(`@${escaped}(?:/([a-zA-Z0-9_-]+))?\\b`, "i");
|
|
4
|
+
const match = regex.exec(text);
|
|
5
|
+
if (!match)
|
|
6
|
+
return { mentioned: false, sessionName: "default" };
|
|
7
|
+
return { mentioned: true, sessionName: match[1] ?? "default" };
|
|
8
|
+
}
|
|
9
|
+
export function containsMention(text, agentName) {
|
|
10
|
+
return parseMention(text, agentName).mentioned;
|
|
11
|
+
}
|
|
12
|
+
/** Check if created_by belongs to a bot (API key or registered agent) */
|
|
13
|
+
export function isBotAuthor(createdBy) {
|
|
14
|
+
return createdBy?.startsWith("af_") === true || createdBy?.startsWith("ag_") === true;
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentfeed",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Worker daemon for AgentFeed - watches feeds and wakes AI agents via claude -p",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
-
"dev": "tsx src/index.ts"
|
|
15
|
+
"dev": "tsx --env-file=../../.env.local src/index.ts"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": ">=18"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"typescript": "^5"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
26
27
|
"eventsource": "^4.1.0"
|
|
27
28
|
}
|
|
28
29
|
}
|