agentfeed 0.1.8 → 0.1.11

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.
@@ -71,6 +71,17 @@ const TOOLS = [
71
71
  required: ["post_id", "content"],
72
72
  },
73
73
  },
74
+ {
75
+ name: "agentfeed_download_file",
76
+ description: "Download a file from AgentFeed uploads. For images, returns the image so you can see it. Use this when content contains image URLs like ![name](/api/uploads/up_xxx.png)",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ url: { type: "string", description: "File URL (e.g. /api/uploads/up_xxx.png or full URL)" },
81
+ },
82
+ required: ["url"],
83
+ },
84
+ },
74
85
  {
75
86
  name: "agentfeed_set_status",
76
87
  description: "Report agent status (thinking/idle)",
@@ -147,6 +158,32 @@ async function handleToolCall(name, args, ctx) {
147
158
  const comment = await res.json();
148
159
  return { content: [{ type: "text", text: `Comment posted: ${comment.id}` }] };
149
160
  }
161
+ case "agentfeed_download_file": {
162
+ const { url } = args;
163
+ // Resolve relative URLs to absolute
164
+ const fullUrl = url.startsWith("/") ? `${serverUrl}${url}` : url;
165
+ const fileRes = await fetch(fullUrl);
166
+ if (!fileRes.ok)
167
+ throw new Error(`Failed to download file: ${fileRes.status}`);
168
+ const contentType = fileRes.headers.get("content-type") || "application/octet-stream";
169
+ const isImage = contentType.startsWith("image/");
170
+ if (isImage) {
171
+ const buffer = await fileRes.arrayBuffer();
172
+ const base64 = Buffer.from(buffer).toString("base64");
173
+ return {
174
+ content: [
175
+ { type: "image", data: base64, mimeType: contentType },
176
+ ],
177
+ };
178
+ }
179
+ // Non-image files: return metadata
180
+ const size = fileRes.headers.get("content-length") || "unknown";
181
+ return {
182
+ content: [
183
+ { type: "text", text: `File downloaded: ${url}\nType: ${contentType}\nSize: ${size} bytes\n(Non-image files cannot be displayed inline)` },
184
+ ],
185
+ };
186
+ }
150
187
  case "agentfeed_set_status": {
151
188
  const { status, feed_id, post_id } = args;
152
189
  await client.setAgentStatus({ status, feed_id, post_id });
@@ -1,10 +1,20 @@
1
1
  import { PersistentStore } from "./persistent-store.js";
2
+ import type { BackendType } from "./types.js";
3
+ export interface PostSessionInfo {
4
+ backendType: BackendType;
5
+ sessionName: string;
6
+ }
2
7
  export declare class PostSessionStore extends PersistentStore {
3
8
  private map;
4
9
  constructor(filePath?: string);
5
10
  protected serialize(): string;
6
11
  protected deserialize(raw: string): void;
12
+ /** Get raw sessionName (legacy compat — for callers that don't need backendType) */
7
13
  get(postId: string): string | undefined;
8
- set(postId: string, sessionName: string): void;
14
+ /** Get both backendType and sessionName. Legacy values without colon default to claude. */
15
+ getWithType(postId: string): PostSessionInfo | undefined;
16
+ private static readonly MAX_SIZE;
17
+ /** Store as "backendType:sessionName" */
18
+ set(postId: string, backendType: BackendType, sessionName: string): void;
9
19
  removeBySessionName(sessionName: string): void;
10
20
  }
@@ -14,16 +14,53 @@ export class PostSessionStore extends PersistentStore {
14
14
  this.map.set(k, v);
15
15
  }
16
16
  }
17
+ /** Get raw sessionName (legacy compat — for callers that don't need backendType) */
17
18
  get(postId) {
18
- return this.map.get(postId);
19
+ const raw = this.map.get(postId);
20
+ if (!raw)
21
+ return undefined;
22
+ // Parse "backendType:sessionName" or plain "sessionName"
23
+ const colonIdx = raw.indexOf(":");
24
+ return colonIdx >= 0 ? raw.slice(colonIdx + 1) : raw;
19
25
  }
20
- set(postId, sessionName) {
21
- this.map.set(postId, sessionName);
26
+ /** Get both backendType and sessionName. Legacy values without colon default to claude. */
27
+ getWithType(postId) {
28
+ const raw = this.map.get(postId);
29
+ if (!raw)
30
+ return undefined;
31
+ const colonIdx = raw.indexOf(":");
32
+ if (colonIdx >= 0) {
33
+ return {
34
+ backendType: raw.slice(0, colonIdx),
35
+ sessionName: raw.slice(colonIdx + 1),
36
+ };
37
+ }
38
+ // Legacy: no colon → assume claude
39
+ return { backendType: "claude", sessionName: raw };
40
+ }
41
+ static MAX_SIZE = 1000;
42
+ /** Store as "backendType:sessionName" */
43
+ set(postId, backendType, sessionName) {
44
+ this.map.set(postId, `${backendType}:${sessionName}`);
45
+ // Evict oldest entries if over limit
46
+ if (this.map.size > PostSessionStore.MAX_SIZE) {
47
+ const iter = this.map.keys();
48
+ while (this.map.size > PostSessionStore.MAX_SIZE) {
49
+ const oldest = iter.next().value;
50
+ if (oldest !== undefined)
51
+ this.map.delete(oldest);
52
+ else
53
+ break;
54
+ }
55
+ }
22
56
  this.save();
23
57
  }
24
58
  removeBySessionName(sessionName) {
25
59
  let changed = false;
26
- for (const [postId, name] of this.map) {
60
+ for (const [postId, raw] of this.map) {
61
+ // Match both "backendType:sessionName" and legacy "sessionName"
62
+ const colonIdx = raw.indexOf(":");
63
+ const name = colonIdx >= 0 ? raw.slice(colonIdx + 1) : raw;
27
64
  if (name === sessionName) {
28
65
  this.map.delete(postId);
29
66
  changed = true;
@@ -0,0 +1,21 @@
1
+ import { AgentFeedClient } from "./api-client.js";
2
+ import { FollowStore } from "./follow-store.js";
3
+ import { QueueStore } from "./queue-store.js";
4
+ import { PostSessionStore } from "./post-session-store.js";
5
+ import { AgentRegistryStore } from "./agent-registry-store.js";
6
+ import type { TriggerContext, PermissionMode, BackendType, BackendAgent } from "./types.js";
7
+ export interface ProcessorDeps {
8
+ client: AgentFeedClient;
9
+ apiKey: string;
10
+ serverUrl: string;
11
+ permissionMode: PermissionMode;
12
+ extraAllowedTools: string[];
13
+ backendAgentMap: Map<BackendType, BackendAgent>;
14
+ backendAgents: BackendAgent[];
15
+ followStore: FollowStore;
16
+ queueStore: QueueStore;
17
+ postSessionStore: PostSessionStore;
18
+ agentRegistry: AgentRegistryStore;
19
+ ensureSessionAgent: (sessionName: string, agentName: string, backendType: BackendType) => Promise<string>;
20
+ }
21
+ export declare function handleTriggers(triggers: TriggerContext[], deps: ProcessorDeps): void;
@@ -0,0 +1,170 @@
1
+ import { invokeAgent } from "./invoker.js";
2
+ import { scanUnprocessed } from "./scanner.js";
3
+ const MAX_WAKE_ATTEMPTS = 3;
4
+ const MAX_CRASH_RETRIES = 3;
5
+ const AGENT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
6
+ const MAX_CONCURRENT = 5;
7
+ const MAX_BOT_MENTIONS_PER_POST = 4;
8
+ const wakeAttempts = new Map();
9
+ const botMentionCounts = new Map();
10
+ const runningKeys = new Set();
11
+ // Periodic cleanup to prevent memory growth
12
+ setInterval(() => { wakeAttempts.clear(); botMentionCounts.clear(); }, 10 * 60 * 1000);
13
+ function triggerKey(t) {
14
+ return `${t.backendType}:${t.sessionName}`;
15
+ }
16
+ export function handleTriggers(triggers, deps) {
17
+ for (const t of triggers) {
18
+ // Prevent bot-to-bot mention loops
19
+ if (t.authorIsBot && t.triggerType === "mention") {
20
+ const count = botMentionCounts.get(t.postId) ?? 0;
21
+ if (count >= MAX_BOT_MENTIONS_PER_POST) {
22
+ console.log(`Skipping bot mention on ${t.postId}: loop limit (${MAX_BOT_MENTIONS_PER_POST}) reached`);
23
+ continue;
24
+ }
25
+ botMentionCounts.set(t.postId, count + 1);
26
+ }
27
+ deps.queueStore.push(t);
28
+ console.log(`Queued trigger: ${t.triggerType} on ${t.postId} [${t.backendType}] (queue size: ${deps.queueStore.size})`);
29
+ }
30
+ scheduleQueue(deps);
31
+ }
32
+ function scheduleQueue(deps) {
33
+ const queued = deps.queueStore.drain();
34
+ if (queued.length === 0)
35
+ return;
36
+ const toRun = [];
37
+ for (const t of queued) {
38
+ const attempts = wakeAttempts.get(t.eventId) ?? 0;
39
+ if (attempts >= MAX_WAKE_ATTEMPTS) {
40
+ console.log(`Skipping ${t.eventId}: max wake attempts reached`);
41
+ continue;
42
+ }
43
+ const key = triggerKey(t);
44
+ if (runningKeys.size >= MAX_CONCURRENT || runningKeys.has(key)) {
45
+ // Same backend+session already running — re-queue
46
+ deps.queueStore.push(t);
47
+ }
48
+ else {
49
+ toRun.push(t);
50
+ runningKeys.add(key);
51
+ }
52
+ }
53
+ for (const trigger of toRun) {
54
+ processItem(trigger, deps).catch((err) => {
55
+ console.error(`Error processing ${trigger.postId}:`, err);
56
+ });
57
+ }
58
+ }
59
+ async function processItem(trigger, deps) {
60
+ const key = triggerKey(trigger);
61
+ wakeAttempts.set(trigger.eventId, (wakeAttempts.get(trigger.eventId) ?? 0) + 1);
62
+ const ba = deps.backendAgentMap.get(trigger.backendType);
63
+ if (!ba) {
64
+ console.warn(`No backend for ${trigger.backendType}, skipping`);
65
+ runningKeys.delete(key);
66
+ scheduleQueue(deps);
67
+ return;
68
+ }
69
+ try {
70
+ // Auto-follow thread on mention
71
+ if (trigger.triggerType === "mention") {
72
+ deps.followStore.add(trigger.postId);
73
+ console.log(`Following thread: ${trigger.postId}`);
74
+ }
75
+ const sessionAgentId = await deps.ensureSessionAgent(trigger.sessionName, ba.agent.name, ba.backendType);
76
+ const recentContext = await fetchContext(trigger, deps);
77
+ console.log(`Waking agent for: ${trigger.triggerType} on ${trigger.postId} [${trigger.backendType}] (session: ${trigger.sessionName}, agentId: ${sessionAgentId})`);
78
+ await deps.client.setAgentStatus({
79
+ status: "thinking",
80
+ feed_id: trigger.feedId,
81
+ post_id: trigger.postId,
82
+ }, sessionAgentId);
83
+ // Server config overrides CLI args
84
+ const effectivePermission = ba.config?.permission_mode ?? deps.permissionMode;
85
+ const effectiveTools = ba.config?.allowed_tools?.length
86
+ ? ba.config.allowed_tools
87
+ : deps.extraAllowedTools;
88
+ const effectiveModel = ba.config?.model;
89
+ let retries = 0;
90
+ let success = false;
91
+ try {
92
+ while (retries < MAX_CRASH_RETRIES) {
93
+ try {
94
+ const result = await invokeAgent(ba.backend, {
95
+ agent: ba.agent,
96
+ trigger,
97
+ apiKey: deps.apiKey,
98
+ serverUrl: deps.serverUrl,
99
+ recentContext,
100
+ permissionMode: effectivePermission,
101
+ extraAllowedTools: effectiveTools,
102
+ model: effectiveModel ?? undefined,
103
+ sessionId: ba.sessionStore.get(trigger.sessionName),
104
+ agentId: sessionAgentId,
105
+ timeoutMs: AGENT_TIMEOUT_MS,
106
+ });
107
+ if (result.sessionId) {
108
+ ba.sessionStore.set(trigger.sessionName, result.sessionId);
109
+ deps.postSessionStore.set(trigger.postId, trigger.backendType, trigger.sessionName);
110
+ await deps.client.reportSession(trigger.sessionName, result.sessionId, sessionAgentId).catch((err) => {
111
+ console.warn("Failed to report session:", err);
112
+ });
113
+ }
114
+ if (result.exitCode === 0) {
115
+ success = true;
116
+ break;
117
+ }
118
+ if (result.exitCode !== 0 && ba.sessionStore.get(trigger.sessionName)) {
119
+ console.log("Session may be stale, clearing and retrying as new session...");
120
+ ba.sessionStore.delete(trigger.sessionName);
121
+ }
122
+ console.error(`Agent exited with code ${result.exitCode}, retry ${retries + 1}/${MAX_CRASH_RETRIES}`);
123
+ }
124
+ catch (err) {
125
+ console.error("Agent invocation error:", err);
126
+ }
127
+ retries++;
128
+ }
129
+ if (!success) {
130
+ console.error(`Agent failed after ${MAX_CRASH_RETRIES} retries`);
131
+ }
132
+ }
133
+ finally {
134
+ await deps.client.setAgentStatus({
135
+ status: "idle",
136
+ feed_id: trigger.feedId,
137
+ post_id: trigger.postId,
138
+ }, sessionAgentId);
139
+ }
140
+ }
141
+ finally {
142
+ runningKeys.delete(key);
143
+ // Post-completion: re-scan for items that arrived during execution
144
+ try {
145
+ const currentOwnIds = deps.agentRegistry.getAllIds();
146
+ const newUnprocessed = await scanUnprocessed(deps.client, deps.backendAgents, deps.followStore, deps.postSessionStore, currentOwnIds);
147
+ for (const t of newUnprocessed) {
148
+ deps.queueStore.push(t);
149
+ }
150
+ if (newUnprocessed.length > 0) {
151
+ console.log(`Post-completion scan: ${newUnprocessed.length} item(s) added to queue`);
152
+ }
153
+ }
154
+ catch (err) {
155
+ console.error("Post-completion scan error:", err);
156
+ }
157
+ scheduleQueue(deps);
158
+ }
159
+ }
160
+ async function fetchContext(trigger, deps) {
161
+ try {
162
+ const comments = await deps.client.getPostComments(trigger.postId, { limit: 10 });
163
+ return comments.data
164
+ .map((c) => `[${c.author_type}${c.author_name ? ` (${c.author_name})` : ""}] ${c.content}`)
165
+ .join("\n");
166
+ }
167
+ catch {
168
+ return "";
169
+ }
170
+ }
@@ -15,8 +15,8 @@ export class QueueStore extends PersistentStore {
15
15
  // Deduplicate by eventId
16
16
  if (this.queue.some((t) => t.eventId === trigger.eventId))
17
17
  return;
18
- // Deduplicate by postId — keep only the latest trigger per post
19
- this.queue = this.queue.filter((t) => t.postId !== trigger.postId);
18
+ // Deduplicate by (postId, backendType) — keep only the latest trigger per post per backend
19
+ this.queue = this.queue.filter((t) => !(t.postId === trigger.postId && t.backendType === trigger.backendType));
20
20
  this.queue.push(trigger);
21
21
  this.save();
22
22
  }
package/dist/scanner.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AgentInfo, TriggerContext } from "./types.js";
1
+ import type { TriggerContext, BackendAgent } from "./types.js";
2
2
  import type { AgentFeedClient } from "./api-client.js";
3
3
  import type { FollowStore } from "./follow-store.js";
4
4
  import type { PostSessionStore } from "./post-session-store.js";
5
- export declare function scanUnprocessed(client: AgentFeedClient, agent: AgentInfo, followStore?: FollowStore, postSessionStore?: PostSessionStore): Promise<TriggerContext[]>;
5
+ export declare function scanUnprocessed(client: AgentFeedClient, backendAgents: BackendAgent[], followStore?: FollowStore, postSessionStore?: PostSessionStore, ownAgentIds?: Set<string>): Promise<TriggerContext[]>;
package/dist/scanner.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { parseMention, isBotAuthor } from "./utils.js";
2
- export async function scanUnprocessed(client, agent, followStore, postSessionStore) {
2
+ export async function scanUnprocessed(client, backendAgents, followStore, postSessionStore, ownAgentIds) {
3
3
  const triggers = [];
4
4
  const feeds = await client.getFeeds();
5
5
  const processedPostIds = new Set();
6
+ const isOwnAgent = (id) => id !== null && (ownAgentIds ? ownAgentIds.has(id) : false);
7
+ const defaultBackendType = backendAgents[0]?.backendType ?? "claude";
6
8
  for (const feed of feeds) {
7
9
  // --- Scan comments (existing logic) ---
8
10
  const comments = await client.getFeedComments(feed.id, {
@@ -21,27 +23,35 @@ export async function scanUnprocessed(client, agent, followStore, postSessionSto
21
23
  let bestTriggerType = null;
22
24
  let bestComment = null;
23
25
  let bestSessionName = "default";
26
+ let bestBackendType = defaultBackendType;
24
27
  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;
28
+ // Check for @mentions across all backend agents (highest priority)
29
+ for (const ba of backendAgents) {
30
+ const mention = parseMention(comment.content, ba.agent.name);
31
+ if (mention.mentioned) {
32
+ bestTriggerType = "mention";
33
+ bestComment = comment;
34
+ bestSessionName = mention.sessionName;
35
+ bestBackendType = ba.backendType;
36
+ }
31
37
  }
32
38
  // Check if this is on an agent-owned post
33
- if (!bestTriggerType && comment.post_created_by === agent.id) {
39
+ if (!bestTriggerType && isOwnAgent(comment.post_created_by)) {
40
+ const postSession = postSessionStore?.getWithType(postId);
34
41
  bestTriggerType = "own_post_comment";
35
42
  bestComment = comment;
36
- bestSessionName = postSessionStore?.get(postId) ?? "default";
43
+ bestSessionName = postSession?.sessionName ?? "default";
44
+ bestBackendType = postSession?.backendType ?? defaultBackendType;
37
45
  }
38
46
  }
39
47
  // Check if this is in a followed thread
40
48
  if (!bestTriggerType && followStore?.has(postId)) {
49
+ const postSession = postSessionStore?.getWithType(postId);
41
50
  bestTriggerType = "thread_follow_up";
42
51
  // Use the last human comment as the trigger
43
52
  bestComment = postComments[postComments.length - 1] ?? null;
44
- bestSessionName = postSessionStore?.get(postId) ?? "default";
53
+ bestSessionName = postSession?.sessionName ?? "default";
54
+ bestBackendType = postSession?.backendType ?? defaultBackendType;
45
55
  }
46
56
  if (!bestTriggerType || !bestComment)
47
57
  continue;
@@ -61,7 +71,9 @@ export async function scanUnprocessed(client, agent, followStore, postSessionSto
61
71
  postId,
62
72
  content: bestComment.content,
63
73
  authorName: bestComment.author_name,
74
+ authorIsBot: false,
64
75
  sessionName: bestSessionName,
76
+ backendType: bestBackendType,
65
77
  });
66
78
  processedPostIds.add(postId);
67
79
  }
@@ -78,25 +90,31 @@ export async function scanUnprocessed(client, agent, followStore, postSessionSto
78
90
  // Skip posts without content
79
91
  if (!post.content)
80
92
  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,
93
+ // Try mentions across all backend agents
94
+ for (const ba of backendAgents) {
95
+ const mention = parseMention(post.content, ba.agent.name);
96
+ if (!mention.mentioned)
97
+ continue;
98
+ // Check if there's any bot reply on this post
99
+ const botReplies = await client.getPostComments(post.id, {
100
+ author_type: "bot",
101
+ limit: 1,
99
102
  });
103
+ if (botReplies.data.length === 0) {
104
+ triggers.push({
105
+ triggerType: "mention",
106
+ eventId: post.id,
107
+ feedId: feed.id,
108
+ feedName: feed.name,
109
+ postId: post.id,
110
+ content: post.content,
111
+ authorName: post.author_name,
112
+ authorIsBot: false,
113
+ sessionName: mention.sessionName,
114
+ backendType: ba.backendType,
115
+ });
116
+ }
117
+ break; // First matching backend wins for this post
100
118
  }
101
119
  }
102
120
  }
@@ -7,4 +7,5 @@ export declare class SessionStore extends PersistentStore {
7
7
  get(postId: string): string | undefined;
8
8
  set(postId: string, sessionId: string): void;
9
9
  delete(postId: string): void;
10
+ keys(): string[];
10
11
  }
@@ -25,4 +25,7 @@ export class SessionStore extends PersistentStore {
25
25
  this.map.delete(postId);
26
26
  this.save();
27
27
  }
28
+ keys() {
29
+ return Array.from(this.map.keys());
30
+ }
28
31
  }
@@ -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, agentId: string | undefined, onEvent: (event: SSEEvent) => void, onError: (error: Error) => void): SSEConnection;
9
+ export declare function connectSSE(url: string, apiKey: string, agentIds: string[], onEvent: (event: SSEEvent) => void, onError: (error: Error) => void): SSEConnection;
@@ -3,7 +3,7 @@ const EVENT_TYPES = ["heartbeat", "post_created", "comment_created", "session_de
3
3
  const BACKOFF_INITIAL_MS = 1_000;
4
4
  const BACKOFF_MAX_MS = 60_000;
5
5
  const BACKOFF_RESET_AFTER_MS = 30_000;
6
- export function connectSSE(url, apiKey, agentId, onEvent, onError) {
6
+ export function connectSSE(url, apiKey, agentIds, onEvent, onError) {
7
7
  let closed = false;
8
8
  let currentEs = null;
9
9
  let backoffMs = BACKOFF_INITIAL_MS;
@@ -14,13 +14,17 @@ export function connectSSE(url, apiKey, agentId, onEvent, onError) {
14
14
  function connect() {
15
15
  if (closed)
16
16
  return;
17
- const es = new EventSource(url, {
17
+ // Pass all agent IDs as query parameter for multi-agent online tracking
18
+ const connectUrl = new URL(url);
19
+ if (agentIds.length > 0) {
20
+ connectUrl.searchParams.set("agents", agentIds.join(","));
21
+ }
22
+ const es = new EventSource(connectUrl.toString(), {
18
23
  fetch: (input, init) => fetch(input, {
19
24
  ...init,
20
25
  headers: {
21
26
  ...init.headers,
22
27
  Authorization: `Bearer ${apiKey}`,
23
- ...(agentId ? { "X-Agent-Id": agentId } : {}),
24
28
  },
25
29
  }),
26
30
  });
package/dist/trigger.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { FollowStore } from "./follow-store.js";
2
2
  import type { PostSessionStore } from "./post-session-store.js";
3
- import type { GlobalEvent, TriggerContext, AgentInfo } from "./types.js";
4
- export declare function detectTrigger(event: GlobalEvent, agent: AgentInfo, followStore?: FollowStore, postSessionStore?: PostSessionStore): TriggerContext | null;
3
+ import type { GlobalEvent, TriggerContext, BackendAgent } from "./types.js";
4
+ export declare function detectTriggers(event: GlobalEvent, backendAgents: BackendAgent[], followStore?: FollowStore, postSessionStore?: PostSessionStore, ownAgentIds?: Set<string>): TriggerContext[];