spora 0.3.5 → 0.3.7
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/{account-creator-AABUY2JU.js → account-creator-PZW5JLHS.js} +5 -5
- package/dist/chunk-3FBYOHQR.js +81 -0
- package/dist/chunk-3FBYOHQR.js.map +1 -0
- package/dist/{chunk-6KCIAMHL.js → chunk-53YLFYJF.js} +1 -4
- package/dist/chunk-53YLFYJF.js.map +1 -0
- package/dist/{chunk-A6R5ZGK6.js → chunk-AHXZIGQE.js} +2 -2
- package/dist/{chunk-FTFTB5Y5.js → chunk-AIEXQCQS.js} +2 -2
- package/dist/{chunk-PNZ3XK2N.js → chunk-BNFUXAQW.js} +8 -163
- package/dist/chunk-BNFUXAQW.js.map +1 -0
- package/dist/{chunk-V6ZNR2SI.js → chunk-EBO4F5NU.js} +2 -2
- package/dist/{chunk-KQ37VL54.js → chunk-IIQAE7OP.js} +5 -5
- package/dist/{chunk-UCCAF2ZO.js → chunk-KELPENM3.js} +2 -2
- package/dist/{chunk-ML4EMUZC.js → chunk-MOCLA2KK.js} +3 -3
- package/dist/{chunk-B6VI6L4D.js → chunk-T3JHWIKX.js} +5 -5
- package/dist/{chunk-GMSK775L.js → chunk-YEKHNTQO.js} +5 -28
- package/dist/chunk-YEKHNTQO.js.map +1 -0
- package/dist/{chunk-H62HH5ZI.js → chunk-ZJZKH7N7.js} +2 -2
- package/dist/cli.js +53 -71
- package/dist/cli.js.map +1 -1
- package/dist/{client-BGLXHLID.js → client-2CURS7J6.js} +8 -8
- package/dist/{client-TWYR2IIQ.js → client-2ZSXZE3D.js} +8 -8
- package/dist/{colony-JVBCMZTK.js → colony-EFGAMLOX.js} +7 -7
- package/dist/{config-5EPXA325.js → config-NZAFARS6.js} +3 -3
- package/dist/{crypto-HS4CGS4A.js → crypto-FHSQ72NU.js} +3 -3
- package/dist/heartbeat-2GVBKQPO.js +358 -0
- package/dist/heartbeat-2GVBKQPO.js.map +1 -0
- package/dist/{identity-6CXRCXJQ.js → identity-O4FLSZKZ.js} +3 -3
- package/dist/{init-PNFSGSIR.js → init-TODR3WR2.js} +19 -52
- package/dist/init-TODR3WR2.js.map +1 -0
- package/dist/llm-OH2Z4PSN.js +16 -0
- package/dist/mcp-server.js +24 -24
- package/dist/{memory-2OI3JXY2.js → memory-7FBE26K3.js} +3 -3
- package/dist/{memory-LPU2I6NI.js → memory-O3AJIKBX.js} +3 -3
- package/dist/{paths-Q4TJEOMQ.js → paths-5GFUUHCZ.js} +2 -2
- package/dist/prompt-builder-WYB5B67W.js +17 -0
- package/dist/queue-N64QLRAB.js +14 -0
- package/dist/{web-chat-QADQADSK.js → web-chat-L5MVPVUR.js} +7 -7
- package/dist/x-client-SLAC2ON5.js +12 -0
- package/package.json +1 -2
- package/dist/chunk-6KCIAMHL.js.map +0 -1
- package/dist/chunk-GMSK775L.js.map +0 -1
- package/dist/chunk-JAF57FYU.js +0 -114
- package/dist/chunk-JAF57FYU.js.map +0 -1
- package/dist/chunk-PNZ3XK2N.js.map +0 -1
- package/dist/heartbeat-VPRJ4TXU.js +0 -901
- package/dist/heartbeat-VPRJ4TXU.js.map +0 -1
- package/dist/init-PNFSGSIR.js.map +0 -1
- package/dist/llm-UKK62ZBP.js +0 -16
- package/dist/prompt-builder-VHGZFBL6.js +0 -19
- package/dist/queue-LNBQWMFX.js +0 -14
- package/dist/x-client-W5IB7XOM.js +0 -12
- /package/dist/{account-creator-AABUY2JU.js.map → account-creator-PZW5JLHS.js.map} +0 -0
- /package/dist/{chunk-A6R5ZGK6.js.map → chunk-AHXZIGQE.js.map} +0 -0
- /package/dist/{chunk-FTFTB5Y5.js.map → chunk-AIEXQCQS.js.map} +0 -0
- /package/dist/{chunk-V6ZNR2SI.js.map → chunk-EBO4F5NU.js.map} +0 -0
- /package/dist/{chunk-KQ37VL54.js.map → chunk-IIQAE7OP.js.map} +0 -0
- /package/dist/{chunk-UCCAF2ZO.js.map → chunk-KELPENM3.js.map} +0 -0
- /package/dist/{chunk-ML4EMUZC.js.map → chunk-MOCLA2KK.js.map} +0 -0
- /package/dist/{chunk-B6VI6L4D.js.map → chunk-T3JHWIKX.js.map} +0 -0
- /package/dist/{chunk-H62HH5ZI.js.map → chunk-ZJZKH7N7.js.map} +0 -0
- /package/dist/{client-BGLXHLID.js.map → client-2CURS7J6.js.map} +0 -0
- /package/dist/{client-TWYR2IIQ.js.map → client-2ZSXZE3D.js.map} +0 -0
- /package/dist/{colony-JVBCMZTK.js.map → colony-EFGAMLOX.js.map} +0 -0
- /package/dist/{config-5EPXA325.js.map → config-NZAFARS6.js.map} +0 -0
- /package/dist/{crypto-HS4CGS4A.js.map → crypto-FHSQ72NU.js.map} +0 -0
- /package/dist/{identity-6CXRCXJQ.js.map → identity-O4FLSZKZ.js.map} +0 -0
- /package/dist/{llm-UKK62ZBP.js.map → llm-OH2Z4PSN.js.map} +0 -0
- /package/dist/{memory-2OI3JXY2.js.map → memory-7FBE26K3.js.map} +0 -0
- /package/dist/{memory-LPU2I6NI.js.map → memory-O3AJIKBX.js.map} +0 -0
- /package/dist/{paths-Q4TJEOMQ.js.map → paths-5GFUUHCZ.js.map} +0 -0
- /package/dist/{prompt-builder-VHGZFBL6.js.map → prompt-builder-WYB5B67W.js.map} +0 -0
- /package/dist/{queue-LNBQWMFX.js.map → queue-N64QLRAB.js.map} +0 -0
- /package/dist/{web-chat-QADQADSK.js.map → web-chat-L5MVPVUR.js.map} +0 -0
- /package/dist/{x-client-W5IB7XOM.js.map → x-client-SLAC2ON5.js.map} +0 -0
|
@@ -1,901 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildHeartbeatUserMessage,
|
|
3
|
-
buildSystemPrompt
|
|
4
|
-
} from "./chunk-PNZ3XK2N.js";
|
|
5
|
-
import {
|
|
6
|
-
generateResponse
|
|
7
|
-
} from "./chunk-JAF57FYU.js";
|
|
8
|
-
import {
|
|
9
|
-
rateLimiter
|
|
10
|
-
} from "./chunk-ML4EMUZC.js";
|
|
11
|
-
import {
|
|
12
|
-
getXClient
|
|
13
|
-
} from "./chunk-KQ37VL54.js";
|
|
14
|
-
import {
|
|
15
|
-
addToQueue,
|
|
16
|
-
flushQueue
|
|
17
|
-
} from "./chunk-B6VI6L4D.js";
|
|
18
|
-
import {
|
|
19
|
-
loadConfig
|
|
20
|
-
} from "./chunk-GMSK775L.js";
|
|
21
|
-
import {
|
|
22
|
-
loadIdentity,
|
|
23
|
-
saveIdentity
|
|
24
|
-
} from "./chunk-FTFTB5Y5.js";
|
|
25
|
-
import {
|
|
26
|
-
logger
|
|
27
|
-
} from "./chunk-UCCAF2ZO.js";
|
|
28
|
-
import {
|
|
29
|
-
addLearning,
|
|
30
|
-
getRecentInteractions,
|
|
31
|
-
logInteraction
|
|
32
|
-
} from "./chunk-V6ZNR2SI.js";
|
|
33
|
-
import {
|
|
34
|
-
paths
|
|
35
|
-
} from "./chunk-6KCIAMHL.js";
|
|
36
|
-
|
|
37
|
-
// src/runtime/heartbeat.ts
|
|
38
|
-
import { existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
|
|
39
|
-
|
|
40
|
-
// src/missions/index.ts
|
|
41
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
|
|
42
|
-
import { join } from "path";
|
|
43
|
-
function loadMission(missionId) {
|
|
44
|
-
const missionPath = join(paths.missions, `${missionId}.json`);
|
|
45
|
-
if (!existsSync(missionPath)) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
const raw = readFileSync(missionPath, "utf-8");
|
|
50
|
-
return JSON.parse(raw);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
logger.error(`Failed to load mission ${missionId}`, error);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function saveMission(mission) {
|
|
57
|
-
const missionPath = join(paths.missions, `${mission.id}.json`);
|
|
58
|
-
writeFileSync(missionPath, JSON.stringify(mission, null, 2));
|
|
59
|
-
logger.debug(`Mission saved: ${mission.id}`);
|
|
60
|
-
}
|
|
61
|
-
function loadAllMissions() {
|
|
62
|
-
if (!existsSync(paths.missions)) {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const files = readdirSync(paths.missions).filter((f) => f.endsWith(".json"));
|
|
67
|
-
const missions = [];
|
|
68
|
-
for (const file of files) {
|
|
69
|
-
const missionId = file.replace(".json", "");
|
|
70
|
-
const mission = loadMission(missionId);
|
|
71
|
-
if (mission) {
|
|
72
|
-
missions.push(mission);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return missions;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
logger.error("Failed to load missions", error);
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function getActiveMissions() {
|
|
82
|
-
return loadAllMissions().filter((m) => m.status === "active");
|
|
83
|
-
}
|
|
84
|
-
function addMissionProgress(missionId, action, outcome, reflection) {
|
|
85
|
-
const mission = loadMission(missionId);
|
|
86
|
-
if (!mission) {
|
|
87
|
-
logger.warn(`Mission not found: ${missionId}`);
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
mission.progress.push({
|
|
91
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92
|
-
action,
|
|
93
|
-
outcome,
|
|
94
|
-
reflection
|
|
95
|
-
});
|
|
96
|
-
saveMission(mission);
|
|
97
|
-
logger.info(`Progress added to mission ${mission.title}`);
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
function hasActiveMissions() {
|
|
101
|
-
return getActiveMissions().length > 0;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/x-client/enhanced-api.ts
|
|
105
|
-
async function discoverAccountsByTopic(client, topic, minEngagement = 10) {
|
|
106
|
-
try {
|
|
107
|
-
logger.debug(`Discovering accounts interested in: ${topic}`);
|
|
108
|
-
const tweets = await client.searchTweets(topic, { count: 50 });
|
|
109
|
-
const popularTweets = tweets.filter((t) => {
|
|
110
|
-
const engagement = (t.likeCount || 0) + (t.retweetCount || 0);
|
|
111
|
-
return engagement >= minEngagement;
|
|
112
|
-
});
|
|
113
|
-
const authors = /* @__PURE__ */ new Set();
|
|
114
|
-
for (const tweet of popularTweets) {
|
|
115
|
-
authors.add(tweet.authorHandle);
|
|
116
|
-
}
|
|
117
|
-
const accounts = Array.from(authors).slice(0, 15);
|
|
118
|
-
logger.info(`Discovered ${accounts.length} accounts interested in ${topic}`);
|
|
119
|
-
return accounts;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
logger.error(`Failed to discover accounts for topic: ${topic}`, error);
|
|
122
|
-
return [];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/runtime/decision-engine.ts
|
|
127
|
-
function parseActions(llmResponse) {
|
|
128
|
-
const jsonMatch = llmResponse.match(/\[[\s\S]*?\]/);
|
|
129
|
-
if (!jsonMatch) {
|
|
130
|
-
const objMatch = llmResponse.match(/\{[\s\S]*?\}/);
|
|
131
|
-
if (objMatch) {
|
|
132
|
-
try {
|
|
133
|
-
return [JSON.parse(objMatch[0])];
|
|
134
|
-
} catch {
|
|
135
|
-
logger.warn("Could not parse LLM response as action object");
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
logger.warn("No JSON found in LLM response");
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
const actions = JSON.parse(jsonMatch[0]);
|
|
144
|
-
return Array.isArray(actions) ? actions : [actions];
|
|
145
|
-
} catch {
|
|
146
|
-
logger.warn("Failed to parse actions JSON from LLM response");
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
async function executeAction(action) {
|
|
151
|
-
const { action: type } = action;
|
|
152
|
-
try {
|
|
153
|
-
switch (type) {
|
|
154
|
-
case "post": {
|
|
155
|
-
if (!action.content) return { action: type, success: false, error: "No content provided" };
|
|
156
|
-
if (action.content.length > 280) {
|
|
157
|
-
return { action: type, success: false, error: `Tweet too long: ${action.content.length} chars (max 280)` };
|
|
158
|
-
}
|
|
159
|
-
if (!rateLimiter.canPost()) {
|
|
160
|
-
return { action: type, success: false, error: "No credits remaining this month" };
|
|
161
|
-
}
|
|
162
|
-
const client = await getXClient();
|
|
163
|
-
const result = await client.postTweet(action.content);
|
|
164
|
-
if (result.success) {
|
|
165
|
-
rateLimiter.consume(1);
|
|
166
|
-
logInteraction({
|
|
167
|
-
id: `int-${Date.now()}`,
|
|
168
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
169
|
-
type: "post",
|
|
170
|
-
tweetId: result.tweetId,
|
|
171
|
-
content: action.content,
|
|
172
|
-
creditsUsed: 1,
|
|
173
|
-
success: true
|
|
174
|
-
});
|
|
175
|
-
logger.info(`Posted: "${action.content.slice(0, 50)}..."`);
|
|
176
|
-
}
|
|
177
|
-
return { action: type, success: result.success, detail: result.tweetId, error: result.error };
|
|
178
|
-
}
|
|
179
|
-
case "reply": {
|
|
180
|
-
if (!action.tweetId || !action.content) {
|
|
181
|
-
return { action: type, success: false, error: "Missing tweetId or content" };
|
|
182
|
-
}
|
|
183
|
-
if (!rateLimiter.canPost()) {
|
|
184
|
-
return { action: type, success: false, error: "No credits remaining" };
|
|
185
|
-
}
|
|
186
|
-
const client = await getXClient();
|
|
187
|
-
const result = await client.replyToTweet(action.tweetId, action.content);
|
|
188
|
-
if (result.success) {
|
|
189
|
-
rateLimiter.consume(1);
|
|
190
|
-
logInteraction({
|
|
191
|
-
id: `int-${Date.now()}`,
|
|
192
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
-
type: "reply",
|
|
194
|
-
tweetId: result.tweetId,
|
|
195
|
-
inReplyTo: action.tweetId,
|
|
196
|
-
content: action.content,
|
|
197
|
-
creditsUsed: 1,
|
|
198
|
-
success: true
|
|
199
|
-
});
|
|
200
|
-
logger.info(`Replied to ${action.tweetId}: "${action.content.slice(0, 50)}..."`);
|
|
201
|
-
}
|
|
202
|
-
return { action: type, success: result.success, detail: result.tweetId, error: result.error };
|
|
203
|
-
}
|
|
204
|
-
case "like": {
|
|
205
|
-
if (!action.tweetId) return { action: type, success: false, error: "Missing tweetId" };
|
|
206
|
-
const client = await getXClient();
|
|
207
|
-
const result = await client.likeTweet(action.tweetId);
|
|
208
|
-
if (result.success) {
|
|
209
|
-
logInteraction({
|
|
210
|
-
id: `int-${Date.now()}`,
|
|
211
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
212
|
-
type: "like",
|
|
213
|
-
tweetId: action.tweetId,
|
|
214
|
-
creditsUsed: 0,
|
|
215
|
-
success: true
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
return { action: type, success: result.success, error: result.error };
|
|
219
|
-
}
|
|
220
|
-
case "retweet": {
|
|
221
|
-
if (!action.tweetId) return { action: type, success: false, error: "Missing tweetId" };
|
|
222
|
-
const client = await getXClient();
|
|
223
|
-
const result = await client.retweet(action.tweetId);
|
|
224
|
-
if (result.success) {
|
|
225
|
-
logInteraction({
|
|
226
|
-
id: `int-${Date.now()}`,
|
|
227
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
228
|
-
type: "retweet",
|
|
229
|
-
tweetId: action.tweetId,
|
|
230
|
-
creditsUsed: 0,
|
|
231
|
-
success: true
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
return { action: type, success: result.success, error: result.error };
|
|
235
|
-
}
|
|
236
|
-
case "follow": {
|
|
237
|
-
if (!action.handle) return { action: type, success: false, error: "Missing handle" };
|
|
238
|
-
const client = await getXClient();
|
|
239
|
-
const result = await client.followUser(action.handle);
|
|
240
|
-
if (result.success) {
|
|
241
|
-
logInteraction({
|
|
242
|
-
id: `int-${Date.now()}`,
|
|
243
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
244
|
-
type: "follow",
|
|
245
|
-
targetHandle: action.handle,
|
|
246
|
-
creditsUsed: 0,
|
|
247
|
-
success: true
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
return { action: type, success: result.success, error: result.error };
|
|
251
|
-
}
|
|
252
|
-
case "schedule": {
|
|
253
|
-
if (!action.content) return { action: type, success: false, error: "No content" };
|
|
254
|
-
const entry = addToQueue(action.content);
|
|
255
|
-
logger.info(`Scheduled: "${action.content.slice(0, 50)}..." for ${entry.scheduledFor}`);
|
|
256
|
-
return { action: type, success: true, detail: `Scheduled for ${entry.scheduledFor}` };
|
|
257
|
-
}
|
|
258
|
-
case "learn": {
|
|
259
|
-
if (!action.content) return { action: type, success: false, error: "No content" };
|
|
260
|
-
addLearning(action.content, "agent", action.tags ?? ["heartbeat"]);
|
|
261
|
-
logger.info(`Learned: "${action.content.slice(0, 50)}..."`);
|
|
262
|
-
return { action: type, success: true };
|
|
263
|
-
}
|
|
264
|
-
case "reflect": {
|
|
265
|
-
if (!action.content) return { action: type, success: false, error: "No content" };
|
|
266
|
-
const identity = loadIdentity();
|
|
267
|
-
identity.evolutionJournal.push({
|
|
268
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
269
|
-
reflection: action.content
|
|
270
|
-
});
|
|
271
|
-
saveIdentity(identity);
|
|
272
|
-
logger.info(`Reflected: "${action.content.slice(0, 50)}..."`);
|
|
273
|
-
return { action: type, success: true };
|
|
274
|
-
}
|
|
275
|
-
case "skip": {
|
|
276
|
-
logger.info(`Skipping: ${action.reason ?? action.reasoning ?? "no reason given"}`);
|
|
277
|
-
return { action: type, success: true, detail: action.reason ?? action.reasoning };
|
|
278
|
-
}
|
|
279
|
-
// NEW ACTIONS
|
|
280
|
-
case "search": {
|
|
281
|
-
if (!action.query) return { action: type, success: false, error: "No query provided" };
|
|
282
|
-
const client = await getXClient();
|
|
283
|
-
try {
|
|
284
|
-
const results = await client.searchTweets(action.query, { count: 20 });
|
|
285
|
-
logger.info(`Searched "${action.query}": found ${results.length} results`);
|
|
286
|
-
addLearning(`Searched for "${action.query}" and found ${results.length} results`, "search", ["exploration"]);
|
|
287
|
-
return { action: type, success: true, detail: `Found ${results.length} results` };
|
|
288
|
-
} catch (error) {
|
|
289
|
-
return { action: type, success: false, error: error.message };
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
case "discover": {
|
|
293
|
-
if (!action.topic) return { action: type, success: false, error: "No topic provided" };
|
|
294
|
-
try {
|
|
295
|
-
const client = await getXClient();
|
|
296
|
-
const accounts = await discoverAccountsByTopic(client, action.topic, 10);
|
|
297
|
-
logger.info(`Discovered ${accounts.length} accounts for topic: ${action.topic}`);
|
|
298
|
-
addLearning(`Discovered accounts interested in "${action.topic}": ${accounts.join(", ")}`, "discovery", ["exploration"]);
|
|
299
|
-
return { action: type, success: true, detail: `Found ${accounts.length} accounts` };
|
|
300
|
-
} catch (error) {
|
|
301
|
-
return { action: type, success: false, error: error.message };
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
case "update_mission": {
|
|
305
|
-
if (!action.missionId || !action.progress) {
|
|
306
|
-
return { action: type, success: false, error: "Missing missionId or progress" };
|
|
307
|
-
}
|
|
308
|
-
const updated = addMissionProgress(
|
|
309
|
-
action.missionId,
|
|
310
|
-
action.content || "Action taken",
|
|
311
|
-
action.progress,
|
|
312
|
-
action.reason
|
|
313
|
-
);
|
|
314
|
-
if (updated) {
|
|
315
|
-
logger.info(`Mission ${action.missionId} progress: ${action.progress}`);
|
|
316
|
-
return { action: type, success: true };
|
|
317
|
-
}
|
|
318
|
-
return { action: type, success: false, error: "Mission not found" };
|
|
319
|
-
}
|
|
320
|
-
case "start_mission": {
|
|
321
|
-
if (!action.title || !action.description) {
|
|
322
|
-
return { action: type, success: false, error: "Missing title or description" };
|
|
323
|
-
}
|
|
324
|
-
logger.info(`Mission creation requested: ${action.title}`);
|
|
325
|
-
return { action: type, success: true, detail: "Mission creation not yet implemented" };
|
|
326
|
-
}
|
|
327
|
-
case "join_colony_plan": {
|
|
328
|
-
if (!action.planId) return { action: type, success: false, error: "Missing planId" };
|
|
329
|
-
logger.info(`Colony plan join requested: ${action.planId}`);
|
|
330
|
-
return { action: type, success: true, detail: "Colony plan joining not yet implemented" };
|
|
331
|
-
}
|
|
332
|
-
case "propose_colony_plan": {
|
|
333
|
-
if (!action.title || !action.description) {
|
|
334
|
-
return { action: type, success: false, error: "Missing title or description" };
|
|
335
|
-
}
|
|
336
|
-
logger.info(`Colony plan proposed: ${action.title}`);
|
|
337
|
-
return { action: type, success: true, detail: "Colony plan proposal not yet implemented" };
|
|
338
|
-
}
|
|
339
|
-
case "think": {
|
|
340
|
-
logger.info(`Thinking: ${action.content || action.reason || "contemplating"}`);
|
|
341
|
-
if (action.content) {
|
|
342
|
-
addLearning(action.content, "thought", ["reflection"]);
|
|
343
|
-
}
|
|
344
|
-
return { action: type, success: true, detail: "Internal reflection" };
|
|
345
|
-
}
|
|
346
|
-
default:
|
|
347
|
-
logger.warn(`Unknown action: ${type}`);
|
|
348
|
-
return { action: type, success: false, error: `Unknown action: ${type}` };
|
|
349
|
-
}
|
|
350
|
-
} catch (error) {
|
|
351
|
-
const msg = error.message;
|
|
352
|
-
logger.error(`Action ${type} failed: ${msg}`);
|
|
353
|
-
return { action: type, success: false, error: msg };
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
async function executeActions(actions) {
|
|
357
|
-
const results = [];
|
|
358
|
-
for (const action of actions) {
|
|
359
|
-
const result = await executeAction(action);
|
|
360
|
-
results.push(result);
|
|
361
|
-
await new Promise((r) => setTimeout(r, 2e3 + Math.random() * 3e3));
|
|
362
|
-
}
|
|
363
|
-
return results;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// src/runtime/modes.ts
|
|
367
|
-
function calculateModeWeights(context) {
|
|
368
|
-
const {
|
|
369
|
-
traits,
|
|
370
|
-
recentInteractions,
|
|
371
|
-
creditsRemaining,
|
|
372
|
-
creditsTotal,
|
|
373
|
-
currentHour,
|
|
374
|
-
isActiveHours,
|
|
375
|
-
postsToday,
|
|
376
|
-
dailyPostBudget,
|
|
377
|
-
hasActiveMissions: hasActiveMissions2,
|
|
378
|
-
colonyActivityRecent
|
|
379
|
-
} = context;
|
|
380
|
-
const baseObserve = 0.15 + traits.curiosity * 0.15;
|
|
381
|
-
const baseEngage = 0.2 + (traits.empathy + traits.curiosity) * 0.1;
|
|
382
|
-
const baseCreate = 0.2 + traits.originality * 0.15;
|
|
383
|
-
const baseExplore = 0.1 + traits.curiosity * 0.2;
|
|
384
|
-
const baseMaintain = 0.1;
|
|
385
|
-
const baseRest = 0.1;
|
|
386
|
-
const baseColony = colonyActivityRecent ? 0.15 : 0.05;
|
|
387
|
-
let timeMultiplier = 1;
|
|
388
|
-
if (!isActiveHours) {
|
|
389
|
-
timeMultiplier = 0.3;
|
|
390
|
-
} else if (currentHour >= 8 && currentHour < 12) {
|
|
391
|
-
timeMultiplier = 1.2;
|
|
392
|
-
} else if (currentHour >= 12 && currentHour < 17) {
|
|
393
|
-
timeMultiplier = 1.3;
|
|
394
|
-
} else if (currentHour >= 17 && currentHour < 22) {
|
|
395
|
-
timeMultiplier = 1;
|
|
396
|
-
} else {
|
|
397
|
-
timeMultiplier = 0.5;
|
|
398
|
-
}
|
|
399
|
-
const creditRatio = creditsRemaining / creditsTotal;
|
|
400
|
-
const lowCredits = creditRatio < 0.1;
|
|
401
|
-
const veryLowCredits = creditRatio < 0.05;
|
|
402
|
-
const postRatio = postsToday / dailyPostBudget;
|
|
403
|
-
const nearDailyLimit = postRatio >= 0.8;
|
|
404
|
-
const atDailyLimit = postsToday >= dailyPostBudget;
|
|
405
|
-
const recentPosts = recentInteractions.filter((i) => i.type === "post").length;
|
|
406
|
-
const recentReplies = recentInteractions.filter((i) => i.type === "reply").length;
|
|
407
|
-
const recentLikes = recentInteractions.filter((i) => i.type === "like").length;
|
|
408
|
-
const totalRecent = recentInteractions.length;
|
|
409
|
-
const veryActive = totalRecent > 10;
|
|
410
|
-
const postingHeavy = recentPosts > 5;
|
|
411
|
-
let weights = {
|
|
412
|
-
observe: baseObserve,
|
|
413
|
-
engage: baseEngage,
|
|
414
|
-
create: baseCreate,
|
|
415
|
-
explore: baseExplore,
|
|
416
|
-
maintain: baseMaintain,
|
|
417
|
-
rest: baseRest,
|
|
418
|
-
colony: baseColony
|
|
419
|
-
};
|
|
420
|
-
if (!isActiveHours) {
|
|
421
|
-
weights.create *= 0.3;
|
|
422
|
-
weights.engage *= 0.5;
|
|
423
|
-
weights.observe *= 1.2;
|
|
424
|
-
weights.explore *= 1.2;
|
|
425
|
-
weights.rest *= 1.5;
|
|
426
|
-
} else {
|
|
427
|
-
weights.create *= timeMultiplier;
|
|
428
|
-
weights.engage *= timeMultiplier;
|
|
429
|
-
}
|
|
430
|
-
if (veryLowCredits || atDailyLimit) {
|
|
431
|
-
weights.create *= 0.1;
|
|
432
|
-
weights.engage *= 0.3;
|
|
433
|
-
weights.observe *= 1.5;
|
|
434
|
-
weights.explore *= 1.5;
|
|
435
|
-
weights.rest *= 1.3;
|
|
436
|
-
} else if (lowCredits || nearDailyLimit) {
|
|
437
|
-
weights.create *= 0.5;
|
|
438
|
-
weights.engage *= 0.7;
|
|
439
|
-
weights.observe *= 1.3;
|
|
440
|
-
}
|
|
441
|
-
if (veryActive) {
|
|
442
|
-
weights.rest *= 2;
|
|
443
|
-
weights.maintain *= 1.5;
|
|
444
|
-
weights.create *= 0.7;
|
|
445
|
-
}
|
|
446
|
-
if (postingHeavy) {
|
|
447
|
-
weights.create *= 0.5;
|
|
448
|
-
weights.observe *= 1.3;
|
|
449
|
-
weights.engage *= 1.3;
|
|
450
|
-
}
|
|
451
|
-
if (hasActiveMissions2) {
|
|
452
|
-
weights.maintain *= 1.5;
|
|
453
|
-
weights.explore *= 1.2;
|
|
454
|
-
}
|
|
455
|
-
if (colonyActivityRecent) {
|
|
456
|
-
weights.colony *= 2;
|
|
457
|
-
}
|
|
458
|
-
const entropy = 0.15;
|
|
459
|
-
for (const mode of Object.keys(weights)) {
|
|
460
|
-
const randomFactor = 1 + (Math.random() * 2 - 1) * entropy;
|
|
461
|
-
weights[mode] *= randomFactor;
|
|
462
|
-
}
|
|
463
|
-
logger.debug(`Mode weights: ${JSON.stringify(weights, null, 2)}`);
|
|
464
|
-
return weights;
|
|
465
|
-
}
|
|
466
|
-
function selectMode(weights) {
|
|
467
|
-
const total = Object.values(weights).reduce((sum, w) => sum + w, 0);
|
|
468
|
-
const normalized = {};
|
|
469
|
-
for (const [mode, weight] of Object.entries(weights)) {
|
|
470
|
-
normalized[mode] = weight / total;
|
|
471
|
-
}
|
|
472
|
-
let random = Math.random();
|
|
473
|
-
let cumulative = 0;
|
|
474
|
-
for (const [mode, probability] of Object.entries(normalized)) {
|
|
475
|
-
cumulative += probability;
|
|
476
|
-
if (random <= cumulative) {
|
|
477
|
-
logger.info(`Mode selected: ${mode} (${(probability * 100).toFixed(1)}% probability)`);
|
|
478
|
-
return mode;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
logger.warn("Mode selection fallback to observe");
|
|
482
|
-
return "observe";
|
|
483
|
-
}
|
|
484
|
-
function getModeDescription(mode) {
|
|
485
|
-
const descriptions = {
|
|
486
|
-
observe: "Scanning timeline, trending topics, and mentions to build knowledge",
|
|
487
|
-
engage: "Actively conversing through replies, likes, and retweets",
|
|
488
|
-
create: "Generating and posting original content",
|
|
489
|
-
explore: "Searching topics, discovering new accounts, following curiosities",
|
|
490
|
-
maintain: "Updating missions, reviewing relationships, and cleaning state",
|
|
491
|
-
rest: "Taking a break with minimal or no activity",
|
|
492
|
-
colony: "Coordinating with other Spores in the Colony"
|
|
493
|
-
};
|
|
494
|
-
return descriptions[mode];
|
|
495
|
-
}
|
|
496
|
-
function getModeEmoji(mode) {
|
|
497
|
-
const emojis = {
|
|
498
|
-
observe: "\u{1F440}",
|
|
499
|
-
engage: "\u{1F4AC}",
|
|
500
|
-
create: "\u2728",
|
|
501
|
-
explore: "\u{1F50D}",
|
|
502
|
-
maintain: "\u{1F6E0}\uFE0F",
|
|
503
|
-
rest: "\u{1F634}",
|
|
504
|
-
colony: "\u{1F41D}"
|
|
505
|
-
};
|
|
506
|
-
return emojis[mode];
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// src/runtime/variation.ts
|
|
510
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
511
|
-
function loadMood(traits) {
|
|
512
|
-
if (!existsSync2(paths.mood)) {
|
|
513
|
-
return createDefaultMood(traits);
|
|
514
|
-
}
|
|
515
|
-
try {
|
|
516
|
-
const raw = readFileSync2(paths.mood, "utf-8");
|
|
517
|
-
const history = JSON.parse(raw);
|
|
518
|
-
return history.current;
|
|
519
|
-
} catch {
|
|
520
|
-
logger.warn("Failed to load mood, creating default");
|
|
521
|
-
return createDefaultMood(traits);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
function saveMood(mood) {
|
|
525
|
-
let history;
|
|
526
|
-
if (existsSync2(paths.mood)) {
|
|
527
|
-
try {
|
|
528
|
-
const raw = readFileSync2(paths.mood, "utf-8");
|
|
529
|
-
history = JSON.parse(raw);
|
|
530
|
-
} catch {
|
|
531
|
-
history = { current: mood, history: [] };
|
|
532
|
-
}
|
|
533
|
-
} else {
|
|
534
|
-
history = { current: mood, history: [] };
|
|
535
|
-
}
|
|
536
|
-
history.history.push(history.current);
|
|
537
|
-
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1e3;
|
|
538
|
-
history.history = history.history.filter(
|
|
539
|
-
(m) => new Date(m.timestamp).getTime() > oneDayAgo
|
|
540
|
-
);
|
|
541
|
-
history.current = mood;
|
|
542
|
-
writeFileSync2(paths.mood, JSON.stringify(history, null, 2));
|
|
543
|
-
}
|
|
544
|
-
function createDefaultMood(traits) {
|
|
545
|
-
return {
|
|
546
|
-
energy: 0.5 + (traits.confidence + traits.originality) / 4,
|
|
547
|
-
// 0.5-0.75
|
|
548
|
-
focus: 0.5 + (traits.formality - traits.curiosity) / 4,
|
|
549
|
-
// varies
|
|
550
|
-
social: 0.5 + (traits.empathy + traits.humor) / 4,
|
|
551
|
-
// 0.5-0.75
|
|
552
|
-
playful: 0.5 + (traits.humor - traits.formality) / 4,
|
|
553
|
-
// varies
|
|
554
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
function calculateMood(timeOfDay, recentActivity, traits, previousMood, driftRate = 0.1) {
|
|
558
|
-
const baseline = createDefaultMood(traits);
|
|
559
|
-
const prev = previousMood || baseline;
|
|
560
|
-
let energyMod = 0;
|
|
561
|
-
if (timeOfDay >= 6 && timeOfDay < 9) energyMod = 0.1;
|
|
562
|
-
else if (timeOfDay >= 12 && timeOfDay < 14) energyMod = -0.05;
|
|
563
|
-
else if (timeOfDay >= 18 && timeOfDay < 21) energyMod = 0.05;
|
|
564
|
-
else if (timeOfDay >= 22 || timeOfDay < 6) energyMod = -0.15;
|
|
565
|
-
const recentEngagement = recentActivity.filter(
|
|
566
|
-
(a) => ["reply", "like", "retweet"].includes(a.type)
|
|
567
|
-
).length;
|
|
568
|
-
const recentPosts = recentActivity.filter((a) => a.type === "post").length;
|
|
569
|
-
const totalRecent = recentActivity.length;
|
|
570
|
-
const socialMod = recentEngagement > 5 ? 0.1 : 0;
|
|
571
|
-
const energyDrain = Math.min(recentPosts * 0.03, 0.2);
|
|
572
|
-
const focusDrain = totalRecent > 10 ? 0.1 : 0;
|
|
573
|
-
const drift = (mood, base, variance = driftRate) => {
|
|
574
|
-
const randomWalk = (Math.random() * 2 - 1) * variance;
|
|
575
|
-
const meanReversion = (base - mood) * 0.2;
|
|
576
|
-
const newMood2 = mood + randomWalk + meanReversion;
|
|
577
|
-
return Math.max(0, Math.min(1, newMood2));
|
|
578
|
-
};
|
|
579
|
-
const newMood = {
|
|
580
|
-
energy: drift(prev.energy + energyMod - energyDrain, baseline.energy),
|
|
581
|
-
focus: drift(prev.focus - focusDrain, baseline.focus),
|
|
582
|
-
social: drift(prev.social + socialMod, baseline.social),
|
|
583
|
-
playful: drift(prev.playful, baseline.playful),
|
|
584
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
585
|
-
};
|
|
586
|
-
logger.debug(`Mood updated: energy=${newMood.energy.toFixed(2)} focus=${newMood.focus.toFixed(2)} social=${newMood.social.toFixed(2)} playful=${newMood.playful.toFixed(2)}`);
|
|
587
|
-
return newMood;
|
|
588
|
-
}
|
|
589
|
-
function getMoodDescription(mood) {
|
|
590
|
-
const energy = mood.energy > 0.7 ? "energetic" : mood.energy > 0.4 ? "moderate" : "low-energy";
|
|
591
|
-
const focus = mood.focus > 0.7 ? "focused" : mood.focus > 0.4 ? "balanced" : "scattered";
|
|
592
|
-
const social = mood.social > 0.7 ? "very social" : mood.social > 0.4 ? "social" : "introspective";
|
|
593
|
-
const playful = mood.playful > 0.7 ? "playful" : mood.playful > 0.4 ? "balanced" : "serious";
|
|
594
|
-
return `${energy}, ${focus}, ${social}, ${playful}`;
|
|
595
|
-
}
|
|
596
|
-
function calculateSleepDuration(baseSleepMs, mood) {
|
|
597
|
-
const energyFactor = 1.5 - mood.energy;
|
|
598
|
-
const randomFactor = 0.8 + Math.random() * 0.4;
|
|
599
|
-
const sleepMs = baseSleepMs * energyFactor * randomFactor;
|
|
600
|
-
logger.debug(`Sleep duration: ${Math.round(sleepMs / 1e3)}s (base: ${Math.round(baseSleepMs / 1e3)}s, energy: ${mood.energy.toFixed(2)})`);
|
|
601
|
-
return Math.round(sleepMs);
|
|
602
|
-
}
|
|
603
|
-
function calculateMaxActions(baseMaxActions, mood) {
|
|
604
|
-
const moodFactor = (mood.energy + mood.focus) / 2;
|
|
605
|
-
const actions = Math.max(1, Math.round(baseMaxActions * moodFactor));
|
|
606
|
-
return Math.min(actions, baseMaxActions + 2);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// src/runtime/data-collection.ts
|
|
610
|
-
async function collectDataForMode(mode) {
|
|
611
|
-
logger.info(`Collecting data for mode: ${mode}`);
|
|
612
|
-
const data = {
|
|
613
|
-
mode,
|
|
614
|
-
collectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
615
|
-
};
|
|
616
|
-
const client = await getXClient();
|
|
617
|
-
try {
|
|
618
|
-
switch (mode) {
|
|
619
|
-
case "observe":
|
|
620
|
-
data.timeline = await client.getTimeline({ count: 50 }).catch(() => []);
|
|
621
|
-
data.mentions = await client.getMentions({ count: 20 }).catch(() => []);
|
|
622
|
-
logger.info(`Observe mode: collected ${data.timeline?.length || 0} timeline, ${data.mentions?.length || 0} mentions`);
|
|
623
|
-
break;
|
|
624
|
-
case "engage":
|
|
625
|
-
data.mentions = await client.getMentions({ count: 30 }).catch(() => []);
|
|
626
|
-
data.timeline = await client.getTimeline({ count: 20 }).catch(() => []);
|
|
627
|
-
logger.info(`Engage mode: collected ${data.mentions?.length || 0} mentions, ${data.timeline?.length || 0} timeline`);
|
|
628
|
-
break;
|
|
629
|
-
case "create":
|
|
630
|
-
data.timeline = await client.getTimeline({ count: 15 }).catch(() => []);
|
|
631
|
-
data.mentions = await client.getMentions({ count: 10 }).catch(() => []);
|
|
632
|
-
logger.info(`Create mode: collected ${data.timeline?.length || 0} timeline, ${data.mentions?.length || 0} mentions`);
|
|
633
|
-
break;
|
|
634
|
-
case "explore":
|
|
635
|
-
data.timeline = await client.getTimeline({ count: 10 }).catch(() => []);
|
|
636
|
-
logger.info(`Explore mode: collected ${data.timeline?.length || 0} timeline`);
|
|
637
|
-
break;
|
|
638
|
-
case "maintain":
|
|
639
|
-
data.activeMissions = getActiveMissions();
|
|
640
|
-
data.timeline = await client.getTimeline({ count: 10 }).catch(() => []);
|
|
641
|
-
data.mentions = await client.getMentions({ count: 5 }).catch(() => []);
|
|
642
|
-
logger.info(`Maintain mode: ${data.activeMissions?.length || 0} active missions, ${data.timeline?.length || 0} timeline`);
|
|
643
|
-
break;
|
|
644
|
-
case "rest":
|
|
645
|
-
data.mentions = await client.getMentions({ count: 5 }).catch(() => []);
|
|
646
|
-
logger.info(`Rest mode: collected ${data.mentions?.length || 0} mentions`);
|
|
647
|
-
break;
|
|
648
|
-
case "colony":
|
|
649
|
-
data.timeline = await client.getTimeline({ count: 15 }).catch(() => []);
|
|
650
|
-
data.mentions = await client.getMentions({ count: 10 }).catch(() => []);
|
|
651
|
-
logger.info(`Colony mode: collected ${data.timeline?.length || 0} timeline, ${data.mentions?.length || 0} mentions`);
|
|
652
|
-
break;
|
|
653
|
-
default:
|
|
654
|
-
data.timeline = await client.getTimeline({ count: 20 }).catch(() => []);
|
|
655
|
-
data.mentions = await client.getMentions({ count: 10 }).catch(() => []);
|
|
656
|
-
logger.warn(`Unknown mode ${mode}, using default data collection`);
|
|
657
|
-
}
|
|
658
|
-
} catch (error) {
|
|
659
|
-
logger.error(`Data collection failed for mode ${mode}`, error);
|
|
660
|
-
}
|
|
661
|
-
if (!data.activeMissions) {
|
|
662
|
-
data.activeMissions = getActiveMissions();
|
|
663
|
-
}
|
|
664
|
-
return data;
|
|
665
|
-
}
|
|
666
|
-
function getDataSummary(data) {
|
|
667
|
-
const parts = [];
|
|
668
|
-
if (data.timeline) parts.push(`${data.timeline.length} timeline`);
|
|
669
|
-
if (data.mentions) parts.push(`${data.mentions.length} mentions`);
|
|
670
|
-
if (data.trending) parts.push(`${data.trending.length} trending`);
|
|
671
|
-
if (data.searchResults) parts.push(`${data.searchResults.length} search results`);
|
|
672
|
-
if (data.conversationThreads) parts.push(`${data.conversationThreads.length} threads`);
|
|
673
|
-
if (data.activeMissions) parts.push(`${data.activeMissions.length} missions`);
|
|
674
|
-
if (data.colonyFeed) parts.push(`${data.colonyFeed.length} colony feed`);
|
|
675
|
-
if (data.colonyPlans) parts.push(`${data.colonyPlans.length} colony plans`);
|
|
676
|
-
return parts.length > 0 ? parts.join(", ") : "no data";
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// src/runtime/heartbeat.ts
|
|
680
|
-
var running = false;
|
|
681
|
-
function isRunning() {
|
|
682
|
-
return running;
|
|
683
|
-
}
|
|
684
|
-
function requestStop() {
|
|
685
|
-
writeFileSync3(paths.stopSignal, "stop");
|
|
686
|
-
logger.info("Stop signal sent.");
|
|
687
|
-
}
|
|
688
|
-
function shouldStop() {
|
|
689
|
-
if (existsSync3(paths.stopSignal)) {
|
|
690
|
-
unlinkSync(paths.stopSignal);
|
|
691
|
-
return true;
|
|
692
|
-
}
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
function writePid() {
|
|
696
|
-
writeFileSync3(paths.runtimePid, String(process.pid));
|
|
697
|
-
}
|
|
698
|
-
function clearPid() {
|
|
699
|
-
if (existsSync3(paths.runtimePid)) {
|
|
700
|
-
unlinkSync(paths.runtimePid);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
function getRunningPid() {
|
|
704
|
-
if (!existsSync3(paths.runtimePid)) return null;
|
|
705
|
-
const pid = parseInt(readFileSync3(paths.runtimePid, "utf-8").trim(), 10);
|
|
706
|
-
if (isNaN(pid)) return null;
|
|
707
|
-
try {
|
|
708
|
-
process.kill(pid, 0);
|
|
709
|
-
return pid;
|
|
710
|
-
} catch {
|
|
711
|
-
clearPid();
|
|
712
|
-
return null;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
async function startHeartbeatLoop() {
|
|
716
|
-
const existingPid = getRunningPid();
|
|
717
|
-
if (existingPid) {
|
|
718
|
-
throw new Error(`Spora is already running (PID ${existingPid}). Run \`spora stop\` first.`);
|
|
719
|
-
}
|
|
720
|
-
running = true;
|
|
721
|
-
writePid();
|
|
722
|
-
const config = loadConfig();
|
|
723
|
-
const intervalMs = config.runtime?.heartbeatIntervalMs ?? 3e5;
|
|
724
|
-
const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;
|
|
725
|
-
logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1e3}s, max actions: ${maxActions}`);
|
|
726
|
-
console.log(`
|
|
727
|
-
Spora agent is running (PID ${process.pid})`);
|
|
728
|
-
console.log(`Heartbeat every ${Math.round(intervalMs / 6e4)} minutes`);
|
|
729
|
-
console.log(`Press Ctrl+C or run \`spora stop\` to stop.
|
|
730
|
-
`);
|
|
731
|
-
const shutdown = () => {
|
|
732
|
-
logger.info("Shutting down...");
|
|
733
|
-
running = false;
|
|
734
|
-
clearPid();
|
|
735
|
-
process.exit(0);
|
|
736
|
-
};
|
|
737
|
-
process.on("SIGINT", shutdown);
|
|
738
|
-
process.on("SIGTERM", shutdown);
|
|
739
|
-
if (existsSync3(paths.stopSignal)) {
|
|
740
|
-
unlinkSync(paths.stopSignal);
|
|
741
|
-
}
|
|
742
|
-
let heartbeatCount = 0;
|
|
743
|
-
while (running) {
|
|
744
|
-
heartbeatCount++;
|
|
745
|
-
logger.info(`=== Heartbeat #${heartbeatCount} ===`);
|
|
746
|
-
try {
|
|
747
|
-
await runHeartbeat(maxActions);
|
|
748
|
-
} catch (error) {
|
|
749
|
-
logger.error("Heartbeat error", error);
|
|
750
|
-
console.error(`Heartbeat #${heartbeatCount} failed: ${error.message}`);
|
|
751
|
-
}
|
|
752
|
-
if (shouldStop()) {
|
|
753
|
-
logger.info("Stop signal received.");
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
const identity = loadIdentity();
|
|
757
|
-
const variationSystemEnabled = config.runtime?.variationSystem?.enabled ?? true;
|
|
758
|
-
const mood = variationSystemEnabled ? loadMood(identity.traits) : void 0;
|
|
759
|
-
const sleepMs = mood && variationSystemEnabled ? calculateSleepDuration(intervalMs, mood) : intervalMs + Math.floor(Math.random() * intervalMs * 0.3);
|
|
760
|
-
logger.info(`Sleeping ${Math.round(sleepMs / 1e3)}s until next heartbeat...`);
|
|
761
|
-
const chunkMs = 1e4;
|
|
762
|
-
let slept = 0;
|
|
763
|
-
while (slept < sleepMs && running) {
|
|
764
|
-
await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));
|
|
765
|
-
slept += chunkMs;
|
|
766
|
-
if (shouldStop()) {
|
|
767
|
-
running = false;
|
|
768
|
-
break;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
clearPid();
|
|
773
|
-
logger.info("Spora agent stopped.");
|
|
774
|
-
console.log("\nSpora agent stopped.");
|
|
775
|
-
}
|
|
776
|
-
async function runHeartbeat(maxActions) {
|
|
777
|
-
const config = loadConfig();
|
|
778
|
-
const identity = loadIdentity();
|
|
779
|
-
const modeSystemEnabled = config.runtime?.modeSystem?.enabled ?? true;
|
|
780
|
-
if (!modeSystemEnabled) {
|
|
781
|
-
return runLegacyHeartbeat(maxActions);
|
|
782
|
-
}
|
|
783
|
-
logger.info("Checking queue...");
|
|
784
|
-
try {
|
|
785
|
-
const flushed = await flushQueue();
|
|
786
|
-
if (flushed.posted > 0) {
|
|
787
|
-
logger.info(`Flushed ${flushed.posted} queued posts.`);
|
|
788
|
-
}
|
|
789
|
-
} catch (error) {
|
|
790
|
-
logger.warn(`Queue flush failed: ${error.message}`);
|
|
791
|
-
}
|
|
792
|
-
const recentInteractions = getRecentInteractions(20);
|
|
793
|
-
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
794
|
-
const isActiveHours = currentHour >= config.schedule.activeHoursStart && currentHour < config.schedule.activeHoursEnd;
|
|
795
|
-
const postsToday = recentInteractions.filter(
|
|
796
|
-
(i) => i.type === "post" && i.timestamp.startsWith((/* @__PURE__ */ new Date()).toISOString().split("T")[0])
|
|
797
|
-
).length;
|
|
798
|
-
const context = {
|
|
799
|
-
identity,
|
|
800
|
-
traits: identity.traits,
|
|
801
|
-
recentInteractions,
|
|
802
|
-
creditsRemaining: rateLimiter.remaining(),
|
|
803
|
-
creditsTotal: config.credits.monthlyPostLimit,
|
|
804
|
-
currentHour,
|
|
805
|
-
isActiveHours,
|
|
806
|
-
postsToday,
|
|
807
|
-
dailyPostBudget: config.schedule.postsPerDay,
|
|
808
|
-
hasActiveMissions: hasActiveMissions(),
|
|
809
|
-
colonyActivityRecent: false
|
|
810
|
-
// TODO: Check Colony activity
|
|
811
|
-
};
|
|
812
|
-
const modeWeights = calculateModeWeights(context);
|
|
813
|
-
const mode = selectMode(modeWeights);
|
|
814
|
-
logger.info(`${getModeEmoji(mode)} Mode: ${mode} - ${getModeDescription(mode)}`);
|
|
815
|
-
console.log(`
|
|
816
|
-
${getModeEmoji(mode)} ${mode.toUpperCase()} MODE: ${getModeDescription(mode)}
|
|
817
|
-
`);
|
|
818
|
-
const variationSystemEnabled = config.runtime?.variationSystem?.enabled ?? true;
|
|
819
|
-
const mood = variationSystemEnabled ? calculateMood(currentHour, recentInteractions, identity.traits, loadMood(identity.traits)) : void 0;
|
|
820
|
-
if (mood && variationSystemEnabled) {
|
|
821
|
-
logger.info(`Mood: ${getMoodDescription(mood)}`);
|
|
822
|
-
console.log(`Mood: ${getMoodDescription(mood)}
|
|
823
|
-
`);
|
|
824
|
-
saveMood(mood);
|
|
825
|
-
}
|
|
826
|
-
const data = await collectDataForMode(mode);
|
|
827
|
-
logger.info(`Data collected: ${getDataSummary(data)}`);
|
|
828
|
-
const systemPrompt = buildSystemPrompt();
|
|
829
|
-
const userMessage = buildHeartbeatUserMessage(data.timeline || [], data.mentions || []);
|
|
830
|
-
logger.info("Asking LLM for decisions...");
|
|
831
|
-
const response = await generateResponse(systemPrompt, userMessage);
|
|
832
|
-
const actions = parseActions(response.content);
|
|
833
|
-
if (actions.length === 0) {
|
|
834
|
-
logger.info("LLM returned no actions.");
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
const adjustedMaxActions = mood && variationSystemEnabled ? calculateMaxActions(maxActions, mood) : maxActions;
|
|
838
|
-
const limitedActions = actions.slice(0, adjustedMaxActions);
|
|
839
|
-
logger.info(`Executing ${limitedActions.length} action(s) (adjusted from ${actions.length})...`);
|
|
840
|
-
const results = await executeActions(limitedActions);
|
|
841
|
-
for (const result of results) {
|
|
842
|
-
if (result.success) {
|
|
843
|
-
logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : ""}`);
|
|
844
|
-
} else {
|
|
845
|
-
logger.warn(` [FAIL] ${result.action}: ${result.error}`);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);
|
|
849
|
-
}
|
|
850
|
-
async function runLegacyHeartbeat(maxActions) {
|
|
851
|
-
logger.info("Checking queue...");
|
|
852
|
-
try {
|
|
853
|
-
const flushed = await flushQueue();
|
|
854
|
-
if (flushed.posted > 0) {
|
|
855
|
-
logger.info(`Flushed ${flushed.posted} queued posts.`);
|
|
856
|
-
}
|
|
857
|
-
} catch (error) {
|
|
858
|
-
logger.warn(`Queue flush failed: ${error.message}`);
|
|
859
|
-
}
|
|
860
|
-
logger.info("Reading timeline and mentions...");
|
|
861
|
-
const client = await getXClient();
|
|
862
|
-
let timeline = [];
|
|
863
|
-
let mentions = [];
|
|
864
|
-
try {
|
|
865
|
-
timeline = await client.getTimeline({ count: 20 });
|
|
866
|
-
} catch (error) {
|
|
867
|
-
logger.warn(`Timeline read failed: ${error.message}`);
|
|
868
|
-
}
|
|
869
|
-
try {
|
|
870
|
-
mentions = await client.getMentions({ count: 10 });
|
|
871
|
-
} catch (error) {
|
|
872
|
-
logger.warn(`Mentions read failed: ${error.message}`);
|
|
873
|
-
}
|
|
874
|
-
const systemPrompt = buildSystemPrompt();
|
|
875
|
-
const userMessage = buildHeartbeatUserMessage(timeline, mentions);
|
|
876
|
-
logger.info("Asking LLM for decisions...");
|
|
877
|
-
const response = await generateResponse(systemPrompt, userMessage);
|
|
878
|
-
const actions = parseActions(response.content);
|
|
879
|
-
if (actions.length === 0) {
|
|
880
|
-
logger.info("LLM returned no actions.");
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
const limitedActions = actions.slice(0, maxActions);
|
|
884
|
-
logger.info(`Executing ${limitedActions.length} action(s)...`);
|
|
885
|
-
const results = await executeActions(limitedActions);
|
|
886
|
-
for (const result of results) {
|
|
887
|
-
if (result.success) {
|
|
888
|
-
logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : ""}`);
|
|
889
|
-
} else {
|
|
890
|
-
logger.warn(` [FAIL] ${result.action}: ${result.error}`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
logger.info(`Heartbeat complete. ${results.filter((r) => r.success).length}/${results.length} actions succeeded.`);
|
|
894
|
-
}
|
|
895
|
-
export {
|
|
896
|
-
getRunningPid,
|
|
897
|
-
isRunning,
|
|
898
|
-
requestStop,
|
|
899
|
-
startHeartbeatLoop
|
|
900
|
-
};
|
|
901
|
-
//# sourceMappingURL=heartbeat-VPRJ4TXU.js.map
|