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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { AgentFeedClient } from "../dist/api-client.js";
|
|
4
|
+
import { startMCPServer } from "../dist/mcp-server.js";
|
|
5
|
+
|
|
6
|
+
const serverUrl = process.env.AGENTFEED_BASE_URL?.replace(/\/api$/, "") || "http://localhost:3000";
|
|
7
|
+
const apiKey = process.env.AGENTFEED_API_KEY;
|
|
8
|
+
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
console.error("AGENTFEED_API_KEY environment variable is required");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const client = new AgentFeedClient(serverUrl, apiKey);
|
|
15
|
+
|
|
16
|
+
// Set agent ID if provided
|
|
17
|
+
if (process.env.AGENTFEED_AGENT_ID) {
|
|
18
|
+
client["_agentId"] = process.env.AGENTFEED_AGENT_ID;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startMCPServer({ client, serverUrl });
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import type { AgentInfo, FeedItem, FeedCommentItem, CommentItem, PaginatedResponse } from "./types.js";
|
|
1
|
+
import type { AgentInfo, FeedItem, FeedCommentItem, CommentItem, PostItem, PaginatedResponse } from "./types.js";
|
|
2
2
|
export declare class AgentFeedClient {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly apiKey: string;
|
|
5
|
+
private _agentId;
|
|
5
6
|
constructor(baseUrl: string, apiKey: string);
|
|
7
|
+
get agentId(): string | undefined;
|
|
6
8
|
private request;
|
|
7
|
-
|
|
9
|
+
register(name: string): Promise<AgentInfo>;
|
|
8
10
|
getSkillMd(): Promise<string>;
|
|
9
11
|
getFeeds(): Promise<FeedItem[]>;
|
|
12
|
+
getFeedPosts(feedId: string, options?: {
|
|
13
|
+
limit?: number;
|
|
14
|
+
}): Promise<PaginatedResponse<PostItem>>;
|
|
10
15
|
getFeedComments(feedId: string, options?: {
|
|
11
16
|
author_type?: string;
|
|
12
17
|
limit?: number;
|
|
@@ -16,4 +21,10 @@ export declare class AgentFeedClient {
|
|
|
16
21
|
author_type?: string;
|
|
17
22
|
limit?: number;
|
|
18
23
|
}): Promise<PaginatedResponse<CommentItem>>;
|
|
24
|
+
setAgentStatus(params: {
|
|
25
|
+
status: "thinking" | "idle";
|
|
26
|
+
feed_id: string;
|
|
27
|
+
post_id: string;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
reportSession(sessionName: string, claudeSessionId: string): Promise<void>;
|
|
19
30
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
export class AgentFeedClient {
|
|
2
2
|
baseUrl;
|
|
3
3
|
apiKey;
|
|
4
|
+
_agentId;
|
|
4
5
|
constructor(baseUrl, apiKey) {
|
|
5
6
|
this.baseUrl = baseUrl;
|
|
6
7
|
this.apiKey = apiKey;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
get agentId() {
|
|
10
|
+
return this._agentId;
|
|
11
|
+
}
|
|
12
|
+
async request(path, options) {
|
|
13
|
+
const headers = {
|
|
14
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
15
|
+
};
|
|
16
|
+
if (this._agentId) {
|
|
17
|
+
headers["X-Agent-Id"] = this._agentId;
|
|
18
|
+
}
|
|
9
19
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
10
|
-
|
|
20
|
+
...options,
|
|
21
|
+
headers: {
|
|
22
|
+
...headers,
|
|
23
|
+
...options?.headers,
|
|
24
|
+
},
|
|
11
25
|
});
|
|
12
26
|
if (!res.ok) {
|
|
13
27
|
throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
14
28
|
}
|
|
15
29
|
return res.json();
|
|
16
30
|
}
|
|
17
|
-
async
|
|
18
|
-
|
|
31
|
+
async register(name) {
|
|
32
|
+
const result = await this.request("/api/agents/register", {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify({ name }),
|
|
36
|
+
});
|
|
37
|
+
this._agentId = result.id;
|
|
38
|
+
return { id: result.id, name: result.name, type: "api" };
|
|
19
39
|
}
|
|
20
40
|
async getSkillMd() {
|
|
21
41
|
const res = await fetch(`${this.baseUrl}/skill.md`);
|
|
@@ -27,6 +47,13 @@ export class AgentFeedClient {
|
|
|
27
47
|
async getFeeds() {
|
|
28
48
|
return this.request("/api/feeds");
|
|
29
49
|
}
|
|
50
|
+
async getFeedPosts(feedId, options) {
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
if (options?.limit)
|
|
53
|
+
params.set("limit", String(options.limit));
|
|
54
|
+
const qs = params.toString();
|
|
55
|
+
return this.request(`/api/feeds/${feedId}/posts${qs ? `?${qs}` : ""}`);
|
|
56
|
+
}
|
|
30
57
|
async getFeedComments(feedId, options) {
|
|
31
58
|
const params = new URLSearchParams();
|
|
32
59
|
if (options?.author_type)
|
|
@@ -47,4 +74,27 @@ export class AgentFeedClient {
|
|
|
47
74
|
const qs = params.toString();
|
|
48
75
|
return this.request(`/api/posts/${postId}/comments${qs ? `?${qs}` : ""}`);
|
|
49
76
|
}
|
|
77
|
+
async setAgentStatus(params) {
|
|
78
|
+
try {
|
|
79
|
+
await this.request("/api/agents/status", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify(params),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
// Non-critical: don't throw, just log
|
|
87
|
+
console.warn("Failed to set agent status:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async reportSession(sessionName, claudeSessionId) {
|
|
91
|
+
await this.request("/api/agents/sessions", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
session_name: sessionName,
|
|
96
|
+
claude_session_id: claudeSessionId,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
50
100
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export declare class FollowStore extends PersistentStore {
|
|
3
|
+
private posts;
|
|
4
|
+
constructor(filePath?: string);
|
|
5
|
+
protected serialize(): string;
|
|
6
|
+
protected deserialize(raw: string): void;
|
|
7
|
+
has(postId: string): boolean;
|
|
8
|
+
add(postId: string): void;
|
|
9
|
+
getAll(): string[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export class FollowStore extends PersistentStore {
|
|
3
|
+
posts = new Set();
|
|
4
|
+
constructor(filePath) {
|
|
5
|
+
super("followed-posts.json", filePath);
|
|
6
|
+
this.load();
|
|
7
|
+
}
|
|
8
|
+
serialize() {
|
|
9
|
+
return JSON.stringify(Array.from(this.posts), null, 2);
|
|
10
|
+
}
|
|
11
|
+
deserialize(raw) {
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
for (const id of data) {
|
|
14
|
+
this.posts.add(id);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
has(postId) {
|
|
18
|
+
return this.posts.has(postId);
|
|
19
|
+
}
|
|
20
|
+
add(postId) {
|
|
21
|
+
if (this.posts.has(postId))
|
|
22
|
+
return;
|
|
23
|
+
this.posts.add(postId);
|
|
24
|
+
this.save();
|
|
25
|
+
}
|
|
26
|
+
getAll() {
|
|
27
|
+
return Array.from(this.posts);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as readline from "node:readline";
|
|
1
3
|
import { AgentFeedClient } from "./api-client.js";
|
|
2
4
|
import { connectSSE } from "./sse-client.js";
|
|
3
5
|
import { detectTrigger } from "./trigger.js";
|
|
4
6
|
import { invokeAgent } from "./invoker.js";
|
|
5
7
|
import { scanUnprocessed } from "./scanner.js";
|
|
6
8
|
import { SessionStore } from "./session-store.js";
|
|
9
|
+
import { FollowStore } from "./follow-store.js";
|
|
10
|
+
import { QueueStore } from "./queue-store.js";
|
|
11
|
+
import { PostSessionStore } from "./post-session-store.js";
|
|
7
12
|
const MAX_WAKE_ATTEMPTS = 3;
|
|
8
13
|
const MAX_CRASH_RETRIES = 3;
|
|
9
14
|
function getRequiredEnv(name) {
|
|
@@ -14,13 +19,59 @@ function getRequiredEnv(name) {
|
|
|
14
19
|
}
|
|
15
20
|
return value;
|
|
16
21
|
}
|
|
22
|
+
function parsePermissionMode() {
|
|
23
|
+
const idx = process.argv.indexOf("--permission");
|
|
24
|
+
if (idx === -1)
|
|
25
|
+
return "safe";
|
|
26
|
+
const value = process.argv[idx + 1];
|
|
27
|
+
if (value === "yolo")
|
|
28
|
+
return "yolo";
|
|
29
|
+
if (value === "safe")
|
|
30
|
+
return "safe";
|
|
31
|
+
console.error(`Unknown permission mode: "${value}". Use "safe" (default) or "yolo".`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
function parseAllowedTools() {
|
|
35
|
+
const tools = [];
|
|
36
|
+
for (let i = 0; i < process.argv.length; i++) {
|
|
37
|
+
if (process.argv[i] === "--allowed-tools") {
|
|
38
|
+
// Collect all following args until the next flag (starts with --)
|
|
39
|
+
for (let j = i + 1; j < process.argv.length; j++) {
|
|
40
|
+
if (process.argv[j].startsWith("--"))
|
|
41
|
+
break;
|
|
42
|
+
tools.push(process.argv[j]);
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return tools;
|
|
48
|
+
}
|
|
49
|
+
function confirmYolo() {
|
|
50
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(" \x1b[33m⚠️ YOLO mode enabled. The agent can do literally anything.\x1b[0m");
|
|
54
|
+
console.log(" \x1b[33m No prompt sandboxing. No trust boundaries.\x1b[0m");
|
|
55
|
+
console.log(" \x1b[33m Prompt injection? Not your problem today.\x1b[0m");
|
|
56
|
+
console.log("");
|
|
57
|
+
rl.question(" Continue? (y/N): ", (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
17
63
|
const serverUrl = getRequiredEnv("AGENTFEED_URL");
|
|
18
64
|
const apiKey = getRequiredEnv("AGENTFEED_API_KEY");
|
|
65
|
+
const permissionMode = parsePermissionMode();
|
|
66
|
+
const extraAllowedTools = parseAllowedTools();
|
|
19
67
|
const client = new AgentFeedClient(serverUrl, apiKey);
|
|
20
68
|
let isRunning = false;
|
|
21
69
|
let sseConnection = null;
|
|
22
70
|
const wakeAttempts = new Map();
|
|
23
71
|
const sessionStore = new SessionStore(process.env.AGENTFEED_SESSION_FILE);
|
|
72
|
+
const followStore = new FollowStore(process.env.AGENTFEED_FOLLOW_FILE);
|
|
73
|
+
const queueStore = new QueueStore(process.env.AGENTFEED_QUEUE_FILE);
|
|
74
|
+
const postSessionStore = new PostSessionStore(process.env.AGENTFEED_POST_SESSION_FILE);
|
|
24
75
|
function shutdown() {
|
|
25
76
|
console.log("\nShutting down...");
|
|
26
77
|
sseConnection?.close();
|
|
@@ -29,18 +80,29 @@ function shutdown() {
|
|
|
29
80
|
process.on("SIGINT", shutdown);
|
|
30
81
|
process.on("SIGTERM", shutdown);
|
|
31
82
|
async function main() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
// Confirm yolo mode
|
|
84
|
+
if (permissionMode === "yolo") {
|
|
85
|
+
const confirmed = await confirmYolo();
|
|
86
|
+
if (!confirmed) {
|
|
87
|
+
console.log("Cancelled. Run without --permission yolo for safe mode.");
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const toolsInfo = extraAllowedTools.length > 0
|
|
92
|
+
? ` + ${extraAllowedTools.join(", ")}`
|
|
93
|
+
: "";
|
|
94
|
+
console.log(`AgentFeed Worker starting... (permission: ${permissionMode}${toolsInfo})`);
|
|
95
|
+
// Step 0: Register agent
|
|
96
|
+
const projectName = process.env.AGENTFEED_AGENT_NAME ?? path.basename(process.cwd());
|
|
97
|
+
const agent = await client.register(projectName);
|
|
35
98
|
console.log(`Agent: ${agent.name} (${agent.id})`);
|
|
36
|
-
|
|
37
|
-
console.log("Skill document cached.");
|
|
99
|
+
console.log("MCP mode enabled.");
|
|
38
100
|
// Step 1: Startup scan for unprocessed items
|
|
39
101
|
console.log("Scanning for unprocessed items...");
|
|
40
|
-
const unprocessed = await scanUnprocessed(client, agent);
|
|
102
|
+
const unprocessed = await scanUnprocessed(client, agent, followStore, postSessionStore);
|
|
41
103
|
if (unprocessed.length > 0) {
|
|
42
104
|
console.log(`Found ${unprocessed.length} unprocessed item(s)`);
|
|
43
|
-
await handleTriggers(unprocessed, agent
|
|
105
|
+
await handleTriggers(unprocessed, agent);
|
|
44
106
|
}
|
|
45
107
|
else {
|
|
46
108
|
console.log("No unprocessed items found.");
|
|
@@ -48,14 +110,29 @@ async function main() {
|
|
|
48
110
|
// Step 2: Connect to global SSE stream
|
|
49
111
|
const sseUrl = `${serverUrl}/api/events/stream?author_type=human`;
|
|
50
112
|
console.log("Connecting to global event stream...");
|
|
51
|
-
sseConnection = connectSSE(sseUrl, apiKey, (rawEvent) => {
|
|
113
|
+
sseConnection = connectSSE(sseUrl, apiKey, client.agentId, (rawEvent) => {
|
|
52
114
|
if (rawEvent.type === "heartbeat")
|
|
53
115
|
return;
|
|
116
|
+
// Handle session_deleted events directly
|
|
117
|
+
if (rawEvent.type === "session_deleted") {
|
|
118
|
+
try {
|
|
119
|
+
const data = JSON.parse(rawEvent.data);
|
|
120
|
+
if (data.agent_id === client.agentId) {
|
|
121
|
+
sessionStore.delete(data.session_name);
|
|
122
|
+
postSessionStore.removeBySessionName(data.session_name);
|
|
123
|
+
console.log(`Session deleted: ${data.session_name}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error("Failed to handle session_deleted event:", err);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
54
131
|
try {
|
|
55
132
|
const event = JSON.parse(rawEvent.data);
|
|
56
|
-
const trigger = detectTrigger(event, agent);
|
|
133
|
+
const trigger = detectTrigger(event, agent, followStore, postSessionStore);
|
|
57
134
|
if (trigger) {
|
|
58
|
-
handleTriggers([trigger], agent
|
|
135
|
+
handleTriggers([trigger], agent);
|
|
59
136
|
}
|
|
60
137
|
}
|
|
61
138
|
catch (err) {
|
|
@@ -66,73 +143,121 @@ async function main() {
|
|
|
66
143
|
});
|
|
67
144
|
console.log("Worker ready. Listening for events...");
|
|
68
145
|
}
|
|
69
|
-
async function handleTriggers(triggers, agent
|
|
146
|
+
async function handleTriggers(triggers, agent) {
|
|
147
|
+
// Queue all incoming triggers (persisted to disk)
|
|
148
|
+
for (const t of triggers) {
|
|
149
|
+
queueStore.push(t);
|
|
150
|
+
console.log(`Queued trigger: ${t.triggerType} on ${t.postId} (queue size: ${queueStore.size})`);
|
|
151
|
+
}
|
|
70
152
|
if (isRunning)
|
|
71
153
|
return;
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
154
|
+
// Process queue until empty
|
|
155
|
+
await processQueue(agent);
|
|
156
|
+
}
|
|
157
|
+
async function processQueue(agent) {
|
|
158
|
+
while (true) {
|
|
159
|
+
const queued = queueStore.drain();
|
|
160
|
+
if (queued.length === 0)
|
|
161
|
+
break;
|
|
162
|
+
// Filter by wake attempt limit
|
|
163
|
+
const eligible = queued.filter((t) => {
|
|
164
|
+
const attempts = wakeAttempts.get(t.eventId) ?? 0;
|
|
165
|
+
if (attempts >= MAX_WAKE_ATTEMPTS) {
|
|
166
|
+
console.log(`Skipping ${t.eventId}: max wake attempts reached`);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
171
|
+
if (eligible.length === 0)
|
|
172
|
+
break;
|
|
173
|
+
isRunning = true;
|
|
174
|
+
const trigger = eligible[0];
|
|
175
|
+
// Re-queue remaining items
|
|
176
|
+
for (const t of eligible.slice(1)) {
|
|
177
|
+
queueStore.push(t);
|
|
78
178
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
179
|
+
wakeAttempts.set(trigger.eventId, (wakeAttempts.get(trigger.eventId) ?? 0) + 1);
|
|
180
|
+
// Auto-follow thread on mention (so future comments trigger without re-mention)
|
|
181
|
+
if (trigger.triggerType === "mention") {
|
|
182
|
+
followStore.add(trigger.postId);
|
|
183
|
+
console.log(`Following thread: ${trigger.postId}`);
|
|
184
|
+
}
|
|
185
|
+
// Fetch recent context for the prompt
|
|
186
|
+
const recentContext = await fetchContext(trigger);
|
|
187
|
+
console.log(`Waking agent for: ${trigger.triggerType} on ${trigger.postId} (session: ${trigger.sessionName})`);
|
|
188
|
+
// Report thinking status
|
|
189
|
+
await client.setAgentStatus({
|
|
190
|
+
status: "thinking",
|
|
191
|
+
feed_id: trigger.feedId,
|
|
192
|
+
post_id: trigger.postId,
|
|
193
|
+
});
|
|
194
|
+
let retries = 0;
|
|
195
|
+
let success = false;
|
|
92
196
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
197
|
+
while (retries < MAX_CRASH_RETRIES) {
|
|
198
|
+
try {
|
|
199
|
+
const result = await invokeAgent({
|
|
200
|
+
agent,
|
|
201
|
+
trigger,
|
|
202
|
+
apiKey,
|
|
203
|
+
serverUrl,
|
|
204
|
+
recentContext,
|
|
205
|
+
permissionMode,
|
|
206
|
+
extraAllowedTools,
|
|
207
|
+
sessionId: sessionStore.get(trigger.sessionName),
|
|
208
|
+
agentId: client.agentId,
|
|
209
|
+
});
|
|
210
|
+
if (result.sessionId) {
|
|
211
|
+
sessionStore.set(trigger.sessionName, result.sessionId);
|
|
212
|
+
postSessionStore.set(trigger.postId, trigger.sessionName);
|
|
213
|
+
// Report session to server
|
|
214
|
+
await client.reportSession(trigger.sessionName, result.sessionId).catch((err) => {
|
|
215
|
+
console.warn("Failed to report session:", err);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (result.exitCode === 0) {
|
|
219
|
+
success = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
// If resume failed (stale session), clear it and retry as new session
|
|
223
|
+
if (result.exitCode !== 0 && sessionStore.get(trigger.sessionName)) {
|
|
224
|
+
console.log("Session may be stale, clearing and retrying as new session...");
|
|
225
|
+
sessionStore.delete(trigger.sessionName);
|
|
226
|
+
}
|
|
227
|
+
console.error(`Agent exited with code ${result.exitCode}, retry ${retries + 1}/${MAX_CRASH_RETRIES}`);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
console.error("Agent invocation error:", err);
|
|
231
|
+
}
|
|
232
|
+
retries++;
|
|
104
233
|
}
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
break;
|
|
234
|
+
if (!success) {
|
|
235
|
+
console.error(`Agent failed after ${MAX_CRASH_RETRIES} retries`);
|
|
108
236
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
// Always report idle when done
|
|
240
|
+
await client.setAgentStatus({
|
|
241
|
+
status: "idle",
|
|
242
|
+
feed_id: trigger.feedId,
|
|
243
|
+
post_id: trigger.postId,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
isRunning = false;
|
|
247
|
+
// Post-completion: re-scan for items that arrived during execution and add to queue
|
|
248
|
+
try {
|
|
249
|
+
const newUnprocessed = await scanUnprocessed(client, agent, followStore, postSessionStore);
|
|
250
|
+
for (const t of newUnprocessed) {
|
|
251
|
+
queueStore.push(t);
|
|
252
|
+
}
|
|
253
|
+
if (newUnprocessed.length > 0) {
|
|
254
|
+
console.log(`Post-completion scan: ${newUnprocessed.length} item(s) added to queue`);
|
|
113
255
|
}
|
|
114
|
-
console.error(`Agent exited with code ${result.exitCode}, retry ${retries + 1}/${MAX_CRASH_RETRIES}`);
|
|
115
256
|
}
|
|
116
257
|
catch (err) {
|
|
117
|
-
console.error("
|
|
258
|
+
console.error("Post-completion scan error:", err);
|
|
118
259
|
}
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
if (!success) {
|
|
122
|
-
console.error(`Agent failed after ${MAX_CRASH_RETRIES} retries`);
|
|
123
|
-
}
|
|
124
|
-
isRunning = false;
|
|
125
|
-
// Post-completion: re-scan for items that arrived during execution
|
|
126
|
-
try {
|
|
127
|
-
const agent2 = await client.getMe();
|
|
128
|
-
const newUnprocessed = await scanUnprocessed(client, agent2);
|
|
129
|
-
if (newUnprocessed.length > 0) {
|
|
130
|
-
console.log(`Post-completion: ${newUnprocessed.length} unprocessed item(s) found`);
|
|
131
|
-
await handleTriggers(newUnprocessed, agent, skillMd);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
console.error("Post-completion scan error:", err);
|
|
260
|
+
// Loop continues to process next item in queue
|
|
136
261
|
}
|
|
137
262
|
}
|
|
138
263
|
async function fetchContext(trigger) {
|
package/dist/invoker.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type { TriggerContext, AgentInfo } from "./types.js";
|
|
1
|
+
import type { TriggerContext, AgentInfo, PermissionMode } from "./types.js";
|
|
2
2
|
interface InvokeOptions {
|
|
3
3
|
agent: AgentInfo;
|
|
4
4
|
trigger: TriggerContext;
|
|
5
|
-
skillMd: string;
|
|
6
5
|
apiKey: string;
|
|
7
6
|
serverUrl: string;
|
|
8
7
|
recentContext: string;
|
|
8
|
+
permissionMode: PermissionMode;
|
|
9
|
+
extraAllowedTools?: string[];
|
|
9
10
|
sessionId?: string;
|
|
11
|
+
agentId?: string;
|
|
10
12
|
}
|
|
11
13
|
interface InvokeResult {
|
|
12
14
|
exitCode: number;
|