spora 0.3.0 → 0.3.1

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.
@@ -4,11 +4,13 @@ import {
4
4
  buildNarratedHeartbeatMessage,
5
5
  buildReflectionPrompt,
6
6
  buildSystemPrompt,
7
+ buildTrainingChatPrompt,
7
8
  parseReflection
8
- } from "./chunk-ZN63YLI6.js";
9
+ } from "./chunk-TQWLKICD.js";
10
+ import "./chunk-LRKBNKMQ.js";
11
+ import "./chunk-R7PAD4OL.js";
9
12
  import "./chunk-JWMADEQO.js";
10
13
  import "./chunk-FCAK5FYQ.js";
11
- import "./chunk-LRKBNKMQ.js";
12
14
  import "./chunk-SXMDYUK3.js";
13
15
  import "./chunk-GJFBWIW3.js";
14
16
  import "./chunk-J7J557HV.js";
@@ -20,6 +22,7 @@ export {
20
22
  buildNarratedHeartbeatMessage,
21
23
  buildReflectionPrompt,
22
24
  buildSystemPrompt,
25
+ buildTrainingChatPrompt,
23
26
  parseReflection
24
27
  };
25
- //# sourceMappingURL=prompt-builder-IY2SLZ7F.js.map
28
+ //# sourceMappingURL=prompt-builder-EQYCNP63.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -302,60 +302,107 @@ async function extractAndSaveLearnings(responseText) {
302
302
  }
303
303
  return responseText.replace(/<<LEARN:\s*.+?>>/g, "").trim();
304
304
  }
305
- async function extractAndExecuteActions(responseText) {
306
- const { parseActions, executeActions } = await import("./decision-engine-WBD36PZI.js");
307
- const jsonBlockPattern = /```json\s*([\s\S]*?)```/g;
308
- const blocks = [...responseText.matchAll(jsonBlockPattern)];
309
- const visibleResults = [];
310
- let searchResults = null;
311
- const processResults = (actionResults) => {
312
- for (const r of actionResults) {
313
- if (r.action === "search") {
314
- if (r.success && r.detail && r.detail !== "No results found") {
315
- searchResults = r.detail;
316
- console.log(chalk.green(` [Actions] search: found results (internal)`));
317
- } else {
318
- console.log(chalk.dim(` [Actions] search: ${r.detail ?? r.error ?? "no results"}`));
305
+ async function extractAndApplyTraining(responseText) {
306
+ const trainingPattern = /<<TRAINING:([\s\S]*?)>>/g;
307
+ const matches = [...responseText.matchAll(trainingPattern)];
308
+ for (const match of matches) {
309
+ try {
310
+ const update = JSON.parse(match[1]);
311
+ if (update.identity) {
312
+ const { loadIdentity: loadIdentity2, saveIdentity, mutateIdentity } = await import("./identity-LN2R4KJU.js");
313
+ let identity = loadIdentity2();
314
+ if (update.identity.traits) {
315
+ for (const [trait, value] of Object.entries(update.identity.traits)) {
316
+ if (typeof value === "number" && value >= 0 && value <= 1) {
317
+ identity = mutateIdentity(identity, `traits.${trait}`, value, "training-chat");
318
+ console.log(chalk.dim(` [Training] ${trait}: ${(value * 100).toFixed(0)}%`));
319
+ }
320
+ }
319
321
  }
320
- } else if (r.success) {
321
- if ((r.action === "post" || r.action === "reply" || r.action === "schedule") && r.content) {
322
- const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });
323
- visibleResults.push(`<<TWEET:${tweetData}>>`);
324
- console.log(chalk.green(` [Actions] ${r.action}: success (tweet preview)`));
325
- } else {
326
- const detail = r.detail ? ` (${r.detail})` : "";
327
- visibleResults.push(`[${r.action}] Done${detail}`);
328
- console.log(chalk.green(` [Actions] ${r.action}: success${detail}`));
322
+ const arrayFields = ["coreValues", "catchphrases", "topics", "avoidTopics", "heroes", "goals", "boundaries"];
323
+ for (const field of arrayFields) {
324
+ if (update.identity[field] && Array.isArray(update.identity[field])) {
325
+ identity = mutateIdentity(identity, field, update.identity[field], "training-chat");
326
+ console.log(chalk.dim(` [Training] ${field} updated`));
327
+ }
328
+ }
329
+ const stringFields = ["worldview", "tone", "bio", "originStory"];
330
+ for (const field of stringFields) {
331
+ if (typeof update.identity[field] === "string") {
332
+ identity = mutateIdentity(identity, field, update.identity[field], "training-chat");
333
+ console.log(chalk.dim(` [Training] ${field} updated`));
334
+ }
335
+ }
336
+ const enumFields = ["vocabularyStyle", "emojiUsage", "tweetStyle", "conflictStyle"];
337
+ for (const field of enumFields) {
338
+ if (typeof update.identity[field] === "string") {
339
+ identity = mutateIdentity(identity, field, update.identity[field], "training-chat");
340
+ console.log(chalk.dim(` [Training] ${field} \u2192 ${update.identity[field]}`));
341
+ }
342
+ }
343
+ if (update.identity.engagementStrategy) {
344
+ const es = update.identity.engagementStrategy;
345
+ if (es.replyStyle) identity = mutateIdentity(identity, "engagementStrategy.replyStyle", es.replyStyle, "training-chat");
346
+ if (es.followStrategy) identity = mutateIdentity(identity, "engagementStrategy.followStrategy", es.followStrategy, "training-chat");
347
+ if (es.contentMix) {
348
+ const merged = { ...identity.engagementStrategy.contentMix, ...es.contentMix };
349
+ identity = mutateIdentity(identity, "engagementStrategy.contentMix", merged, "training-chat");
350
+ }
351
+ console.log(chalk.dim(` [Training] engagement strategy updated`));
352
+ }
353
+ try {
354
+ saveIdentity(identity);
355
+ console.log(chalk.green(` [Training] Identity saved.`));
356
+ } catch (err) {
357
+ console.log(chalk.red(` [Training] Identity save failed: ${err.message}`));
329
358
  }
330
- } else {
331
- visibleResults.push(`[${r.action}] Failed: ${r.error}`);
332
- console.log(chalk.red(` [Actions] ${r.action}: ${r.error}`));
333
359
  }
334
- }
335
- };
336
- if (blocks.length > 0) {
337
- for (const block of blocks) {
338
- const actions = parseActions(block[1]);
339
- if (actions.length > 0) {
340
- console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));
341
- const actionResults = await executeActions(actions);
342
- processResults(actionResults);
360
+ if (update.strategy) {
361
+ const { loadStrategy, saveStrategy } = await import("./strategy-TOVFBIZQ.js");
362
+ const strategy = loadStrategy();
363
+ if (update.strategy.currentFocus) strategy.currentFocus = update.strategy.currentFocus;
364
+ if (update.strategy.shortTermGoals) strategy.shortTermGoals = update.strategy.shortTermGoals;
365
+ if (update.strategy.currentMood) strategy.currentMood = update.strategy.currentMood;
366
+ if (update.strategy.contentInsights) strategy.contentInsights.push(...update.strategy.contentInsights);
367
+ if (update.strategy.experiments) strategy.experiments.push(...update.strategy.experiments);
368
+ if (update.strategy.peopleToEngage) strategy.peopleToEngage.push(...update.strategy.peopleToEngage);
369
+ strategy.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
370
+ saveStrategy(strategy);
371
+ console.log(chalk.green(` [Training] Strategy updated.`));
343
372
  }
344
- }
345
- } else {
346
- const actions = parseActions(responseText);
347
- if (actions.length > 0 && actions[0].action) {
348
- console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));
349
- const actionResults = await executeActions(actions);
350
- processResults(actionResults);
373
+ if (update.learning?.content) {
374
+ const { addLearning } = await import("./memory-J6AYZ5Y2.js");
375
+ addLearning(update.learning.content, "training-chat", update.learning.tags ?? ["training"]);
376
+ console.log(chalk.dim(` [Training] Learning: "${update.learning.content.slice(0, 50)}..."`));
377
+ }
378
+ if (update.reflection) {
379
+ const { loadIdentity: loadId, saveIdentity: saveId } = await import("./identity-LN2R4KJU.js");
380
+ const id = loadId();
381
+ id.evolutionJournal.push({ date: (/* @__PURE__ */ new Date()).toISOString(), reflection: update.reflection });
382
+ saveId(id);
383
+ console.log(chalk.dim(` [Training] Journal entry added.`));
384
+ }
385
+ if (update.goalUpdates?.length > 0) {
386
+ const { loadGoals, saveGoals } = await import("./goals-IM4AEHS4.js");
387
+ const tracker = loadGoals();
388
+ for (const gu of update.goalUpdates) {
389
+ const existing = tracker.goals.find((g) => g.goal === gu.goal);
390
+ if (existing) {
391
+ existing.progress = gu.progress;
392
+ existing.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
393
+ } else {
394
+ tracker.goals.push({ goal: gu.goal, progress: gu.progress, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
395
+ }
396
+ }
397
+ tracker.lastReviewed = (/* @__PURE__ */ new Date()).toISOString();
398
+ saveGoals(tracker);
399
+ console.log(chalk.dim(` [Training] Goals updated.`));
400
+ }
401
+ } catch (err) {
402
+ console.log(chalk.red(` [Training] Failed to parse/apply training: ${err.message}`));
351
403
  }
352
404
  }
353
- let cleanText = responseText.replace(/```json\s*[\s\S]*?```/g, "").trim();
354
- cleanText = cleanText.replace(/\n{3,}/g, "\n\n").trim();
355
- if (visibleResults.length > 0) {
356
- cleanText += "\n\n" + visibleResults.join("\n");
357
- }
358
- return { cleanText, results: visibleResults, searchResults };
405
+ return responseText.replace(/<<TRAINING:[\s\S]*?>>/g, "").trim().replace(/\n{3,}/g, "\n\n");
359
406
  }
360
407
  async function logChatInteraction(userMessage, agentResponse) {
361
408
  const { logInteraction } = await import("./memory-J6AYZ5Y2.js");
@@ -409,8 +456,8 @@ async function startWebChat() {
409
456
  const remainingMs = sleepBefore.sleeping && sleepBefore.wakeAt ? Math.max(0, sleepBefore.wakeAt - Date.now()) : 0;
410
457
  server.setAwake();
411
458
  if (!systemPrompt || messageCount % 10 === 0) {
412
- const { buildChatPrompt } = await import("./prompt-builder-IY2SLZ7F.js");
413
- systemPrompt = buildChatPrompt(realHandle);
459
+ const { buildTrainingChatPrompt } = await import("./prompt-builder-EQYCNP63.js");
460
+ systemPrompt = buildTrainingChatPrompt(realHandle);
414
461
  }
415
462
  messageCount++;
416
463
  const { hasLLMKey, chat: chatLLM } = await import("./llm-MHZG2VHU.js");
@@ -419,28 +466,8 @@ async function startWebChat() {
419
466
  }
420
467
  chatHistory.push({ role: "user", content: message });
421
468
  const response = await chatLLM(systemPrompt, chatHistory);
422
- let { cleanText: actionCleanedText, searchResults } = await extractAndExecuteActions(response.content);
423
- if (searchResults) {
424
- console.log(chalk.cyan(" [Chat] Search returned results \u2014 doing follow-up LLM call..."));
425
- const firstNarration = actionCleanedText.replace(/```json\s*[\s\S]*?```/g, "").replace(/\n{3,}/g, "\n\n").trim();
426
- if (firstNarration) {
427
- chatHistory.push({ role: "assistant", content: firstNarration });
428
- }
429
- chatHistory.push({
430
- role: "user",
431
- content: `[Internal: here are the search results you requested. Pick one or more to act on (reply, like, etc.) and do it now. Do NOT show these results to me \u2014 just act on them and tell me what you did.]
432
-
433
- ${searchResults}`
434
- });
435
- const followUp = await chatLLM(systemPrompt, chatHistory);
436
- const followUpResult = await extractAndExecuteActions(followUp.content);
437
- actionCleanedText = followUpResult.cleanText;
438
- chatHistory.pop();
439
- if (firstNarration) {
440
- chatHistory.pop();
441
- }
442
- }
443
- const cleanResponse = await extractAndSaveLearnings(actionCleanedText);
469
+ const trainedText = await extractAndApplyTraining(response.content);
470
+ const cleanResponse = await extractAndSaveLearnings(trainedText);
444
471
  chatHistory.push({ role: "assistant", content: cleanResponse });
445
472
  logChatInteraction(message, cleanResponse).catch(
446
473
  (err) => console.error(chalk.dim(" [Memory] Failed to log interaction:"), err)
@@ -549,7 +576,7 @@ async function startNarratedHeartbeat(server) {
549
576
  }
550
577
  async function runNarratedHeartbeat(server, maxActions, intervalMs, heartbeatCount = 1) {
551
578
  const { getXClient } = await import("./x-client-HUXCQOAW.js");
552
- const { buildSystemPrompt, buildNarratedHeartbeatMessage, buildReflectionPrompt, parseReflection } = await import("./prompt-builder-IY2SLZ7F.js");
579
+ const { buildSystemPrompt, buildNarratedHeartbeatMessage, buildReflectionPrompt, parseReflection } = await import("./prompt-builder-EQYCNP63.js");
553
580
  const { generateResponse } = await import("./llm-MHZG2VHU.js");
554
581
  const { parseActions, executeActions } = await import("./decision-engine-WBD36PZI.js");
555
582
  const { flushQueue } = await import("./queue-MLRTMJRE.js");
@@ -772,4 +799,4 @@ export {
772
799
  openBrowser,
773
800
  startWebChat
774
801
  };
775
- //# sourceMappingURL=web-chat-2BAWTCGU.js.map
802
+ //# sourceMappingURL=web-chat-RZKDQYJ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-chat/server.ts","../src/web-chat/index.ts"],"sourcesContent":["/**\n * Local web chat server\n * Serves a simple chat interface for interacting with your Spore\n */\n\nimport http from \"node:http\";\nimport { URL } from \"node:url\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { paths } from \"../utils/paths.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface ChatMessage {\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: number;\n}\n\ninterface AgentIdentity {\n name: string;\n handle: string;\n bio?: string;\n profileImage?: string;\n createdAt?: string;\n}\n\nexport class WebChatServer {\n private server: http.Server | null = null;\n private port: number;\n private messages: ChatMessage[] = [];\n private onUserMessage?: (message: string) => Promise<string>;\n private onHeartbeatChange?: (intervalMs: number) => void;\n private identity?: AgentIdentity;\n private sleepState: { sleeping: boolean; wakeAt: number | null; intervalMs: number } = {\n sleeping: false,\n wakeAt: null,\n intervalMs: 60_000,\n };\n private memoryStats: { learnings: number; relationships: number; interactions: number } = {\n learnings: 0,\n relationships: 0,\n interactions: 0,\n };\n\n constructor(port = 3737) {\n this.port = port;\n }\n\n setIdentity(identity: AgentIdentity) {\n this.identity = identity;\n }\n\n setMessageHandler(handler: (message: string) => Promise<string>) {\n this.onUserMessage = handler;\n }\n\n setHeartbeatChangeHandler(handler: (intervalMs: number) => void) {\n this.onHeartbeatChange = handler;\n }\n\n setSleeping(wakeAt: number) {\n this.sleepState.sleeping = true;\n this.sleepState.wakeAt = wakeAt;\n }\n\n setAwake() {\n this.sleepState.sleeping = false;\n this.sleepState.wakeAt = null;\n }\n\n getSleepState() {\n return { ...this.sleepState };\n }\n\n setHeartbeatInterval(intervalMs: number) {\n this.sleepState.intervalMs = intervalMs;\n }\n\n getHeartbeatInterval(): number {\n return this.sleepState.intervalMs;\n }\n\n setMemoryStats(stats: { learnings: number; relationships: number; interactions: number }) {\n this.memoryStats = stats;\n }\n\n async start(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.server = http.createServer(async (req, res) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n // CORS headers\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(200);\n res.end();\n return;\n }\n\n // Serve HTML\n if (url.pathname === \"/\" || url.pathname === \"/index.html\") {\n try {\n // Try multiple paths since __dirname changes based on build output\n const possiblePaths = [\n join(__dirname, \"web-chat\", \"chat.html\"),\n join(__dirname, \"chat.html\"),\n join(__dirname, \"..\", \"web-chat\", \"chat.html\"),\n join(__dirname, \"..\", \"src\", \"web-chat\", \"chat.html\"),\n ];\n\n let html: string | null = null;\n for (const p of possiblePaths) {\n try {\n html = readFileSync(p, \"utf-8\");\n break;\n } catch {\n continue;\n }\n }\n\n if (html) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n } else {\n console.error(\"Could not find chat.html in any of:\", possiblePaths);\n res.writeHead(500);\n res.end(\"Error: Could not find chat.html\");\n }\n } catch (error) {\n res.writeHead(500);\n res.end(\"Error loading chat interface\");\n }\n return;\n }\n\n // Serve logo\n if (url.pathname === \"/logo.png\") {\n try {\n const logoPaths = [\n join(__dirname, \"web-chat\", \"logo.png\"),\n join(__dirname, \"logo.png\"),\n join(__dirname, \"..\", \"web-chat\", \"logo.png\"),\n join(__dirname, \"..\", \"src\", \"web-chat\", \"logo.png\"),\n ];\n let logoData: Buffer | null = null;\n for (const p of logoPaths) {\n try {\n logoData = readFileSync(p) as unknown as Buffer;\n break;\n } catch {\n continue;\n }\n }\n if (logoData) {\n res.writeHead(200, { \"Content-Type\": \"image/png\", \"Cache-Control\": \"public, max-age=86400\" });\n res.end(logoData);\n } else {\n res.writeHead(404);\n res.end(\"Logo not found\");\n }\n } catch {\n res.writeHead(404);\n res.end(\"Logo not found\");\n }\n return;\n }\n\n // API: Get agent identity\n if (url.pathname === \"/api/identity\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ identity: this.identity || null }));\n return;\n }\n\n // API: Get messages (optionally since a timestamp)\n if (url.pathname === \"/api/messages\" && req.method === \"GET\") {\n const since = url.searchParams.get(\"since\");\n if (since) {\n const sinceTs = parseInt(since, 10);\n const newMessages = this.messages.filter((m) => m.timestamp > sinceTs);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: newMessages }));\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: this.messages }));\n }\n return;\n }\n\n // API: Send message\n if (url.pathname === \"/api/message\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const { message } = JSON.parse(body);\n\n // Add user message\n this.messages.push({\n role: \"user\",\n content: message,\n timestamp: Date.now(),\n });\n\n // Get response\n if (this.onUserMessage) {\n const response = await this.onUserMessage(message);\n this.messages.push({\n role: \"assistant\",\n content: response,\n timestamp: Date.now(),\n });\n this.saveLastAssistantMessage(response);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, response }));\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No message handler configured\" }));\n }\n } catch (error) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid request\" }));\n }\n });\n return;\n }\n\n // API: Get memory stats (for intelligence bar)\n if (url.pathname === \"/api/memory-stats\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(this.memoryStats));\n return;\n }\n\n // API: Get last assistant message (persisted across restarts)\n if (url.pathname === \"/api/last-message\" && req.method === \"GET\") {\n try {\n if (existsSync(paths.lastChatMessage)) {\n const data = readFileSync(paths.lastChatMessage, \"utf-8\");\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(data);\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ content: null }));\n }\n } catch {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ content: null }));\n }\n return;\n }\n\n // API: Get sleep state\n if (url.pathname === \"/api/sleep-state\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(this.sleepState));\n return;\n }\n\n // API: Update heartbeat interval\n if (url.pathname === \"/api/config/heartbeat\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk.toString(); });\n req.on(\"end\", () => {\n try {\n const { intervalMs } = JSON.parse(body);\n if (typeof intervalMs === \"number\" && intervalMs >= 60_000) {\n this.sleepState.intervalMs = intervalMs;\n if (this.onHeartbeatChange) {\n this.onHeartbeatChange(intervalMs);\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, intervalMs }));\n } else {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid interval\" }));\n }\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid request\" }));\n }\n });\n return;\n }\n\n // 404\n res.writeHead(404);\n res.end(\"Not found\");\n });\n\n this.server.listen(this.port, () => {\n const url = `http://localhost:${this.port}`;\n resolve(url);\n });\n\n this.server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n // Port in use, try next port\n this.port++;\n this.server?.close();\n this.start().then(resolve).catch(reject);\n } else {\n reject(error);\n }\n });\n });\n }\n\n /**\n * Push a message into the chat from the server side (used by heartbeat narration)\n */\n pushMessage(role: \"user\" | \"assistant\", content: string) {\n this.messages.push({ role, content, timestamp: Date.now() });\n if (role === \"assistant\") {\n this.saveLastAssistantMessage(content);\n }\n }\n\n /**\n * Persist the last assistant message to disk so it survives restarts\n */\n private saveLastAssistantMessage(content: string) {\n try {\n writeFileSync(paths.lastChatMessage, JSON.stringify({ content, timestamp: Date.now() }));\n } catch {\n // Not critical — fail silently\n }\n }\n\n getMessageCount(): number {\n return this.messages.length;\n }\n\n stop() {\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n }\n}\n","/**\n * Web chat integration\n */\n\nimport { WebChatServer } from \"./server.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\n\n/**\n * Extract <<LEARN: ...>> tags from response, save them, and return cleaned text\n */\nasync function extractAndSaveLearnings(responseText: string): Promise<string> {\n const learnPattern = /<<LEARN:\\s*(.+?)>>/g;\n const matches = [...responseText.matchAll(learnPattern)];\n\n if (matches.length > 0) {\n const { addLearning } = await import(\"../memory/index.js\");\n for (const match of matches) {\n const learning = match[1].trim();\n addLearning(learning, \"web-chat\", [\"chat\", \"creator-interaction\"]);\n console.log(chalk.dim(` [Memory] Saved learning: ${learning}`));\n }\n }\n\n // Strip the learn tags from the response the user sees\n return responseText.replace(/<<LEARN:\\s*.+?>>/g, \"\").trim();\n}\n\n/**\n * Extract <<TRAINING:{...}>> tags from response, apply them to identity/memory, and return cleaned text.\n */\nasync function extractAndApplyTraining(responseText: string): Promise<string> {\n const trainingPattern = /<<TRAINING:([\\s\\S]*?)>>/g;\n const matches = [...responseText.matchAll(trainingPattern)];\n\n for (const match of matches) {\n try {\n const update = JSON.parse(match[1]);\n\n // Apply identity mutations\n if (update.identity) {\n const { loadIdentity, saveIdentity, mutateIdentity } = await import(\"../identity/index.js\");\n let identity = loadIdentity();\n\n // Traits (0-1 scale)\n if (update.identity.traits) {\n for (const [trait, value] of Object.entries(update.identity.traits)) {\n if (typeof value === \"number\" && value >= 0 && value <= 1) {\n identity = mutateIdentity(identity, `traits.${trait}`, value, \"training-chat\");\n console.log(chalk.dim(` [Training] ${trait}: ${(value as number * 100).toFixed(0)}%`));\n }\n }\n }\n\n // Array fields — full replacement\n const arrayFields = [\"coreValues\", \"catchphrases\", \"topics\", \"avoidTopics\", \"heroes\", \"goals\", \"boundaries\"] as const;\n for (const field of arrayFields) {\n if (update.identity[field] && Array.isArray(update.identity[field])) {\n identity = mutateIdentity(identity, field, update.identity[field], \"training-chat\");\n console.log(chalk.dim(` [Training] ${field} updated`));\n }\n }\n\n // String fields\n const stringFields = [\"worldview\", \"tone\", \"bio\", \"originStory\"] as const;\n for (const field of stringFields) {\n if (typeof update.identity[field] === \"string\") {\n identity = mutateIdentity(identity, field, update.identity[field], \"training-chat\");\n console.log(chalk.dim(` [Training] ${field} updated`));\n }\n }\n\n // Enum fields\n const enumFields = [\"vocabularyStyle\", \"emojiUsage\", \"tweetStyle\", \"conflictStyle\"] as const;\n for (const field of enumFields) {\n if (typeof update.identity[field] === \"string\") {\n identity = mutateIdentity(identity, field, update.identity[field], \"training-chat\");\n console.log(chalk.dim(` [Training] ${field} → ${update.identity[field]}`));\n }\n }\n\n // Engagement strategy\n if (update.identity.engagementStrategy) {\n const es = update.identity.engagementStrategy;\n if (es.replyStyle) identity = mutateIdentity(identity, \"engagementStrategy.replyStyle\", es.replyStyle, \"training-chat\");\n if (es.followStrategy) identity = mutateIdentity(identity, \"engagementStrategy.followStrategy\", es.followStrategy, \"training-chat\");\n if (es.contentMix) {\n const merged = { ...identity.engagementStrategy.contentMix, ...es.contentMix };\n identity = mutateIdentity(identity, \"engagementStrategy.contentMix\", merged, \"training-chat\");\n }\n console.log(chalk.dim(` [Training] engagement strategy updated`));\n }\n\n try {\n saveIdentity(identity);\n console.log(chalk.green(` [Training] Identity saved.`));\n } catch (err) {\n console.log(chalk.red(` [Training] Identity save failed: ${(err as Error).message}`));\n }\n }\n\n // Apply strategy updates\n if (update.strategy) {\n const { loadStrategy, saveStrategy } = await import(\"../memory/strategy.js\");\n const strategy = loadStrategy();\n\n if (update.strategy.currentFocus) strategy.currentFocus = update.strategy.currentFocus;\n if (update.strategy.shortTermGoals) strategy.shortTermGoals = update.strategy.shortTermGoals;\n if (update.strategy.currentMood) strategy.currentMood = update.strategy.currentMood;\n if (update.strategy.contentInsights) strategy.contentInsights.push(...update.strategy.contentInsights);\n if (update.strategy.experiments) strategy.experiments.push(...update.strategy.experiments);\n if (update.strategy.peopleToEngage) strategy.peopleToEngage.push(...update.strategy.peopleToEngage);\n\n strategy.lastUpdated = new Date().toISOString();\n saveStrategy(strategy);\n console.log(chalk.green(` [Training] Strategy updated.`));\n }\n\n // Apply learning\n if (update.learning?.content) {\n const { addLearning } = await import(\"../memory/index.js\");\n addLearning(update.learning.content, \"training-chat\", update.learning.tags ?? [\"training\"]);\n console.log(chalk.dim(` [Training] Learning: \"${update.learning.content.slice(0, 50)}...\"`));\n }\n\n // Apply reflection/journal entry\n if (update.reflection) {\n const { loadIdentity: loadId, saveIdentity: saveId } = await import(\"../identity/index.js\");\n const id = loadId();\n id.evolutionJournal.push({ date: new Date().toISOString(), reflection: update.reflection });\n saveId(id);\n console.log(chalk.dim(` [Training] Journal entry added.`));\n }\n\n // Apply goal updates\n if (update.goalUpdates?.length > 0) {\n const { loadGoals, saveGoals } = await import(\"../memory/goals.js\");\n const tracker = loadGoals();\n for (const gu of update.goalUpdates) {\n const existing = tracker.goals.find((g: any) => g.goal === gu.goal);\n if (existing) {\n existing.progress = gu.progress;\n existing.lastUpdated = new Date().toISOString();\n } else {\n tracker.goals.push({ goal: gu.goal, progress: gu.progress, lastUpdated: new Date().toISOString() });\n }\n }\n tracker.lastReviewed = new Date().toISOString();\n saveGoals(tracker);\n console.log(chalk.dim(` [Training] Goals updated.`));\n }\n\n } catch (err) {\n console.log(chalk.red(` [Training] Failed to parse/apply training: ${(err as Error).message}`));\n }\n }\n\n // Strip training tags from visible text\n return responseText.replace(/<<TRAINING:[\\s\\S]*?>>/g, \"\").trim().replace(/\\n{3,}/g, \"\\n\\n\");\n}\n\n/**\n * Extract JSON action blocks from the LLM response, execute them, and return cleaned text + results.\n * Search results are returned separately so the caller can feed them back to the LLM.\n */\nasync function extractAndExecuteActions(responseText: string): Promise<{\n cleanText: string;\n results: string[];\n searchResults: string | null;\n}> {\n const { parseActions, executeActions } = await import(\"../runtime/decision-engine.js\");\n\n // Try to find JSON code blocks in the response\n const jsonBlockPattern = /```json\\s*([\\s\\S]*?)```/g;\n const blocks = [...responseText.matchAll(jsonBlockPattern)];\n const visibleResults: string[] = [];\n let searchResults: string | null = null;\n\n const processResults = (actionResults: Awaited<ReturnType<typeof executeActions>>) => {\n for (const r of actionResults) {\n if (r.action === \"search\") {\n // Search results are internal — don't show in chat\n if (r.success && r.detail && r.detail !== \"No results found\") {\n searchResults = r.detail;\n console.log(chalk.green(` [Actions] search: found results (internal)`));\n } else {\n console.log(chalk.dim(` [Actions] search: ${r.detail ?? r.error ?? \"no results\"}`));\n }\n } else if (r.success) {\n // For post/reply/schedule with content, emit a tweet preview tag\n if ((r.action === \"post\" || r.action === \"reply\" || r.action === \"schedule\") && r.content) {\n const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });\n visibleResults.push(`<<TWEET:${tweetData}>>`);\n console.log(chalk.green(` [Actions] ${r.action}: success (tweet preview)`));\n } else {\n const detail = r.detail ? ` (${r.detail})` : \"\";\n visibleResults.push(`[${r.action}] Done${detail}`);\n console.log(chalk.green(` [Actions] ${r.action}: success${detail}`));\n }\n } else {\n visibleResults.push(`[${r.action}] Failed: ${r.error}`);\n console.log(chalk.red(` [Actions] ${r.action}: ${r.error}`));\n }\n }\n };\n\n if (blocks.length > 0) {\n for (const block of blocks) {\n const actions = parseActions(block[1]);\n if (actions.length > 0) {\n console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));\n const actionResults = await executeActions(actions);\n processResults(actionResults);\n }\n }\n } else {\n // Also try raw JSON (no code block wrapper)\n const actions = parseActions(responseText);\n if (actions.length > 0 && actions[0].action) {\n console.log(chalk.cyan(` [Actions] Executing ${actions.length} action(s)...`));\n const actionResults = await executeActions(actions);\n processResults(actionResults);\n }\n }\n\n // Strip JSON code blocks from the user-visible text\n let cleanText = responseText.replace(/```json\\s*[\\s\\S]*?```/g, \"\").trim();\n // Clean up extra whitespace from removal\n cleanText = cleanText.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n // Append visible action results (search excluded)\n if (visibleResults.length > 0) {\n cleanText += \"\\n\\n\" + visibleResults.join(\"\\n\");\n }\n\n return { cleanText, results: visibleResults, searchResults };\n}\n\n/**\n * Log a chat exchange as an interaction in memory\n */\nasync function logChatInteraction(userMessage: string, agentResponse: string): Promise<void> {\n const { logInteraction } = await import(\"../memory/index.js\");\n logInteraction({\n id: `chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n content: agentResponse.slice(0, 200),\n targetHandle: \"creator\",\n creditsUsed: 0,\n success: true,\n });\n}\n\nexport async function startWebChat() {\n const identity = loadIdentity();\n\n const server = new WebChatServer();\n\n // Pass identity to server so the chat UI can display it\n server.setIdentity({\n name: identity.name,\n handle: identity.handle,\n bio: identity.bio,\n profileImage: identity.profileImage,\n createdAt: identity.createdAt,\n });\n\n // Resolve real Twitter handle on startup\n let realHandle: string | undefined;\n try {\n const { hasXCredentials } = await import(\"../utils/paths.js\");\n if (hasXCredentials()) {\n const { getXClient } = await import(\"../x-client/index.js\");\n const client = await getXClient();\n if (\"getAuthenticatedHandle\" in client) {\n realHandle = await (client as any).getAuthenticatedHandle();\n console.log(chalk.dim(` [Auth] Real Twitter handle: @${realHandle}`));\n // Update the server identity to show the real handle\n server.setIdentity({\n name: identity.name,\n handle: realHandle,\n bio: identity.bio,\n profileImage: identity.profileImage,\n createdAt: identity.createdAt,\n });\n }\n }\n } catch (err) {\n console.log(chalk.dim(` [Auth] Could not resolve real handle: ${(err as Error).message}`));\n }\n\n // Set up message handler - connected to actual LLM with memory\n const chatHistory: Array<{ role: \"user\" | \"assistant\"; content: string }> = [];\n let systemPrompt: string | null = null;\n let messageCount = 0;\n\n server.setMessageHandler(async (message: string) => {\n try {\n // Save remaining sleep time so we can resume after responding\n const sleepBefore = server.getSleepState();\n const remainingMs = sleepBefore.sleeping && sleepBefore.wakeAt\n ? Math.max(0, sleepBefore.wakeAt - Date.now())\n : 0;\n\n // Manual message = instant wake — clear sleep indicator\n server.setAwake();\n\n // Build training system prompt on first message, rebuild every 10 messages to refresh memory\n if (!systemPrompt || messageCount % 10 === 0) {\n const { buildTrainingChatPrompt } = await import(\"../runtime/prompt-builder.js\");\n systemPrompt = buildTrainingChatPrompt(realHandle);\n }\n messageCount++;\n\n // Check for LLM key\n const { hasLLMKey, chat: chatLLM } = await import(\"../runtime/llm.js\");\n if (!hasLLMKey()) {\n return \"I can't respond right now - no API key configured. Run `spora set-llm-key` to set one up.\";\n }\n\n // Add user message to history\n chatHistory.push({ role: \"user\", content: message });\n\n // Call LLM with full conversation history\n const response = await chatLLM(systemPrompt, chatHistory);\n\n // Extract and apply any training updates (replaces action execution)\n const trainedText = await extractAndApplyTraining(response.content);\n\n // Also extract <<LEARN:...>> tags as fallback\n const cleanResponse = await extractAndSaveLearnings(trainedText);\n\n // Add cleaned assistant response to history\n chatHistory.push({ role: \"assistant\", content: cleanResponse });\n\n // Log interaction to memory (async, don't block response)\n logChatInteraction(message, cleanResponse).catch((err) =>\n console.error(chalk.dim(\" [Memory] Failed to log interaction:\"), err)\n );\n\n // Resume sleeping for remaining time if there was time left\n if (remainingMs > 10_000) {\n server.setSleeping(Date.now() + remainingMs);\n }\n\n return cleanResponse;\n } catch (error) {\n console.error(\"Chat error:\", error);\n return `Sorry, I ran into an issue: ${(error as Error).message}`;\n }\n });\n\n // Load initial memory stats\n refreshMemoryStats(server).catch(() => {});\n\n const url = await server.start();\n\n console.log(chalk.green(`\\n✓ Chat interface started at ${chalk.bold(url)}\\n`));\n console.log(chalk.dim(`Press Ctrl+C to stop the server\\n`));\n\n // Open browser\n openBrowser(url);\n\n // Start background heartbeat (narrates into chat)\n startNarratedHeartbeat(server).catch((err) =>\n console.error(chalk.red(\"Heartbeat failed to start:\"), err)\n );\n\n // Keep process alive\n process.on(\"SIGINT\", () => {\n console.log(chalk.yellow(\"\\n\\nStopping chat server...\"));\n heartbeatRunning = false;\n server.stop();\n process.exit(0);\n });\n\n // Return the server instance for external control\n return server;\n}\n\n/**\n * Refresh memory stats on the server for the intelligence bar\n */\nasync function refreshMemoryStats(server: WebChatServer) {\n try {\n const { loadLearnings, loadRelationships, getRecentInteractions } = await import(\"../memory/index.js\");\n const learnings = loadLearnings().learnings.length;\n const relationships = Object.keys(loadRelationships().accounts).length;\n const interactions = getRecentInteractions(1000).length;\n server.setMemoryStats({ learnings, relationships, interactions });\n } catch {\n // Memory not available yet, ignore\n }\n}\n\n/**\n * Background heartbeat that narrates into the chat UI\n */\nlet heartbeatRunning = false;\nlet lastMentionsSinceId: string | undefined;\n\nasync function startNarratedHeartbeat(server: WebChatServer) {\n const { loadConfig } = await import(\"../utils/config.js\");\n const { hasLLMKey, generateResponse } = await import(\"../runtime/llm.js\");\n const { hasXCredentials } = await import(\"../utils/paths.js\");\n\n if (!hasLLMKey()) {\n console.log(chalk.dim(\" [Heartbeat] No LLM key — heartbeat disabled.\"));\n return;\n }\n\n if (!hasXCredentials()) {\n console.log(chalk.dim(\" [Heartbeat] No X credentials — heartbeat disabled.\"));\n return;\n }\n\n const config = loadConfig();\n let intervalMs = config.runtime?.heartbeatIntervalMs ?? 60_000;\n const maxActions = config.runtime?.actionsPerHeartbeat ?? 10;\n\n // Set initial heartbeat interval on server and listen for UI changes\n server.setHeartbeatInterval(intervalMs);\n server.setHeartbeatChangeHandler((newIntervalMs: number) => {\n intervalMs = newIntervalMs;\n console.log(chalk.cyan(` [Heartbeat] Interval changed to ${Math.round(newIntervalMs / 1000)}s`));\n // Persist to config (async, fire-and-forget)\n import(\"../utils/config.js\").then(({ loadConfig: reloadConfig, saveConfig }) => {\n const cfg = reloadConfig();\n if (!cfg.runtime) cfg.runtime = {};\n cfg.runtime.heartbeatIntervalMs = newIntervalMs;\n saveConfig(cfg);\n }).catch(() => {\n // Config save failed, not critical — will use new interval in memory\n });\n });\n\n heartbeatRunning = true;\n let heartbeatCount = 0;\n\n console.log(chalk.cyan(` [Heartbeat] Running every ${Math.round(intervalMs / 1000)}s\\n`));\n\n while (heartbeatRunning) {\n heartbeatCount++;\n console.log(chalk.cyan(` [Heartbeat] #${heartbeatCount} starting...`));\n server.setAwake();\n\n // Refresh memory stats for intelligence bar\n await refreshMemoryStats(server);\n\n try {\n await runNarratedHeartbeat(server, maxActions, intervalMs, heartbeatCount);\n } catch (error) {\n console.error(chalk.red(` [Heartbeat] #${heartbeatCount} failed:`), (error as Error).message);\n }\n\n // Sleep with jitter — flush queue during sleep so scheduled posts go out on time\n const jitter = Math.floor(Math.random() * intervalMs * 0.2);\n const sleepMs = intervalMs + jitter;\n console.log(chalk.dim(` [Heartbeat] Sleeping ${Math.round(sleepMs / 1000)}s...`));\n\n // Tell the server we're sleeping so the UI can show the indicator\n server.setSleeping(Date.now() + sleepMs);\n\n let slept = 0;\n while (slept < sleepMs && heartbeatRunning) {\n await new Promise((r) => setTimeout(r, Math.min(10000, sleepMs - slept)));\n slept += 10000;\n\n // Flush queue during sleep so scheduled posts go out at their intended times\n try {\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n console.log(chalk.green(` [Queue] Posted ${flushed.posted} scheduled tweet(s)`));\n }\n } catch {\n // Queue flush failed during sleep, not critical\n }\n }\n\n server.setAwake();\n }\n}\n\nasync function runNarratedHeartbeat(server: WebChatServer, maxActions: number, intervalMs: number, heartbeatCount: number = 1) {\n const { getXClient } = await import(\"../x-client/index.js\");\n const { buildSystemPrompt, buildNarratedHeartbeatMessage, buildReflectionPrompt, parseReflection } = await import(\"../runtime/prompt-builder.js\");\n const { generateResponse } = await import(\"../runtime/llm.js\");\n const { parseActions, executeActions } = await import(\"../runtime/decision-engine.js\");\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n const { retireOldPosts, getActiveTrackedPosts, updatePostMetrics, updateSelfMetrics, getPerformanceSummary } = await import(\"../memory/performance.js\");\n const { loadStrategy, saveStrategy } = await import(\"../memory/strategy.js\");\n const { addLearning } = await import(\"../memory/index.js\");\n const { loadIdentity: loadId } = await import(\"../identity/index.js\");\n type Tweet = Awaited<ReturnType<typeof client.getTimeline>>[number];\n\n // 1. Flush queued posts (silent — user doesn't see this)\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n console.log(chalk.green(` [Queue] Flushed ${flushed.posted} scheduled tweet(s)`));\n }\n } catch {\n // Queue flush failed, not critical\n }\n\n const client = await getXClient();\n\n // Get the bot's own handle to filter own tweets from timeline\n let ownHandle: string | undefined;\n if (\"getAuthenticatedHandle\" in client) {\n try {\n ownHandle = await (client as any).getAuthenticatedHandle();\n } catch {}\n }\n\n // 2. Check engagement on tracked posts (performance feedback loop)\n try {\n retireOldPosts();\n const activePosts = getActiveTrackedPosts();\n for (const post of activePosts.slice(0, 5)) {\n try {\n const tweet = await client.getTweet(post.tweetId);\n if (tweet) {\n updatePostMetrics(post.tweetId, {\n checkedAt: new Date().toISOString(),\n likes: tweet.likeCount ?? 0,\n retweets: tweet.retweetCount ?? 0,\n replies: tweet.replyCount ?? 0,\n });\n }\n } catch { /* skip individual tweet lookup failures */ }\n await new Promise(r => setTimeout(r, 1000));\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Performance check failed: ${(error as Error).message}`));\n }\n\n // 3. Self-awareness: check own profile (every 6th heartbeat)\n if (heartbeatCount % 6 === 1) {\n try {\n const handle = ownHandle ?? loadId().handle;\n const profile = await client.getProfile(handle);\n updateSelfMetrics({\n checkedAt: new Date().toISOString(),\n followers: profile.followersCount,\n following: profile.followingCount,\n totalTweets: profile.tweetCount,\n });\n console.log(chalk.dim(` [Heartbeat] Self: ${profile.followersCount} followers`));\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Self-check failed: ${(error as Error).message}`));\n }\n }\n\n // 4. Read timeline and mentions\n let timeline: Tweet[] = [];\n let mentions: Tweet[] = [];\n\n try {\n timeline = await client.getTimeline({ count: 20 });\n if (ownHandle) {\n timeline = timeline.filter(t => t.authorHandle.toLowerCase() !== ownHandle!.toLowerCase());\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Timeline read failed: ${(error as Error).message}`));\n }\n\n try {\n mentions = await client.getMentions({ count: 10, sinceId: lastMentionsSinceId });\n if (mentions.length > 0) {\n lastMentionsSinceId = mentions[0].id;\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Mentions read failed: ${(error as Error).message}`));\n }\n\n // 5. Proactive discovery (every 4th heartbeat)\n let discoveryResults: Tweet[] = [];\n if (heartbeatCount % 4 === 0) {\n try {\n const identity = loadId();\n const strategy = loadStrategy();\n const allTopics = [...identity.topics, ...(strategy.currentFocus ?? [])];\n if (allTopics.length > 0) {\n const topic = allTopics[Math.floor(Math.random() * allTopics.length)];\n discoveryResults = await client.searchTweets(topic, { count: 10 });\n if (ownHandle) {\n discoveryResults = discoveryResults.filter(t => t.authorHandle.toLowerCase() !== ownHandle!.toLowerCase());\n }\n console.log(chalk.dim(` [Heartbeat] Discovery for \"${topic}\": ${discoveryResults.length} results`));\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Discovery failed: ${(error as Error).message}`));\n }\n }\n\n // 6. Build prompts and ask LLM\n const systemPrompt = buildSystemPrompt();\n const userMessage = buildNarratedHeartbeatMessage(timeline, mentions, intervalMs, discoveryResults);\n\n const response = await generateResponse(systemPrompt, userMessage, { temperature: 0.95 });\n\n // 4. Extract narration text (everything outside JSON blocks)\n let narration = response.content.replace(/```json\\s*[\\s\\S]*?```/g, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n // 5. Parse and execute actions\n const actions = parseActions(response.content);\n\n // Build set of valid tweet IDs from timeline + mentions + discovery to catch hallucinated IDs\n const validTweetIds = new Set<string>();\n for (const t of timeline) validTweetIds.add(t.id);\n for (const t of mentions) validTweetIds.add(t.id);\n for (const t of discoveryResults) validTweetIds.add(t.id);\n\n // Filter out actions with fake/hallucinated tweet IDs\n const validatedActions = actions.filter(a => {\n if (a.tweetId) {\n // Strip \"tweet:\" prefix the LLM sometimes includes\n const cleanId = a.tweetId.replace(/^tweet:/i, \"\").trim();\n if (!validTweetIds.has(cleanId)) {\n console.log(chalk.yellow(` [Heartbeat] Rejected ${a.action}: tweet ID ${cleanId} not in timeline/mentions (likely hallucinated)`));\n return false;\n }\n }\n return true;\n });\n\n const limitedActions = validatedActions.slice(0, maxActions);\n\n // Build tweet lookup map for rich notifications (likes, retweets) and relationship updates\n const tweetMap = new Map<string, Tweet>();\n for (const t of timeline) tweetMap.set(t.id, t);\n for (const t of mentions) tweetMap.set(t.id, t);\n for (const t of discoveryResults) tweetMap.set(t.id, t);\n\n let results: Awaited<ReturnType<typeof executeActions>> = [];\n\n if (limitedActions.length > 0) {\n // Execute actions (pass tweetMap for relationship tracking)\n results = await executeActions(limitedActions, tweetMap);\n\n // Build visible results — use special tags for rich rendering in chat\n const visibleResults: string[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const action = limitedActions[i];\n const detail = r.detail ? ` (${r.detail})` : \"\";\n if (r.action === \"schedule\") {\n // Log to terminal only, don't show in chat\n console.log(chalk.dim(` [Heartbeat] ${r.action}: ${r.success ? \"queued\" : r.error}${detail}`));\n } else if (r.success) {\n // Emit tweet preview card for post/reply with content\n if ((r.action === \"post\" || r.action === \"reply\") && r.content) {\n const tweetData = JSON.stringify({ action: r.action, tweetId: r.detail ?? null, content: r.content });\n visibleResults.push(`<<TWEET:${tweetData}>>`);\n console.log(chalk.green(` [Heartbeat] ${r.action}: success (tweet preview)`));\n } else if (r.action === \"like\" && action?.tweetId) {\n const tweet = tweetMap.get(action.tweetId.replace(/^tweet:/i, \"\").trim());\n if (tweet) {\n const likeData = JSON.stringify({ authorHandle: tweet.authorHandle, text: tweet.text });\n visibleResults.push(`<<LIKE:${likeData}>>`);\n }\n console.log(chalk.green(` [Heartbeat] like: @${tweet?.authorHandle ?? \"unknown\"}`));\n } else if (r.action === \"retweet\" && action?.tweetId) {\n const tweet = tweetMap.get(action.tweetId.replace(/^tweet:/i, \"\").trim());\n if (tweet) {\n const rtData = JSON.stringify({ authorHandle: tweet.authorHandle, text: tweet.text });\n visibleResults.push(`<<RETWEET:${rtData}>>`);\n }\n console.log(chalk.green(` [Heartbeat] retweet: @${tweet?.authorHandle ?? \"unknown\"}`));\n } else if (r.action === \"follow\" && action?.handle) {\n const followData = JSON.stringify({ handle: action.handle });\n visibleResults.push(`<<FOLLOW:${followData}>>`);\n console.log(chalk.green(` [Heartbeat] follow: @${action.handle}`));\n } else {\n console.log(chalk.green(` [Heartbeat] ${r.action}: success${detail}`));\n }\n } else {\n // Errors go to terminal only, not chat\n console.log(chalk.red(` [Heartbeat] ${r.action}: ${r.error}`));\n }\n }\n\n // Combine narration with visible action results\n let fullMessage = narration || \"\";\n if (visibleResults.length > 0) {\n fullMessage += (fullMessage ? \"\\n\\n\" : \"\") + visibleResults.join(\"\\n\");\n }\n if (fullMessage) {\n server.pushMessage(\"assistant\", fullMessage);\n }\n } else if (narration) {\n server.pushMessage(\"assistant\", narration);\n }\n\n // Reflection phase — brief LLM call to extract insights and update strategy\n try {\n const perfSummary = getPerformanceSummary();\n const currentStrategy = loadStrategy();\n const reflectPrompt = buildReflectionPrompt(results, perfSummary, currentStrategy);\n const reflection = await generateResponse(systemPrompt, reflectPrompt, { temperature: 0.7 });\n const reflectionData = parseReflection(reflection.content);\n\n if (reflectionData.learning) {\n addLearning(reflectionData.learning, \"reflection\", [\"auto-reflect\"]);\n console.log(chalk.dim(` [Heartbeat] Reflection: \"${reflectionData.learning.slice(0, 60)}...\"`));\n }\n if (reflectionData.strategyUpdate) {\n const updated = { ...currentStrategy, ...reflectionData.strategyUpdate, lastUpdated: new Date().toISOString() };\n saveStrategy(updated);\n console.log(chalk.dim(` [Heartbeat] Strategy updated.`));\n }\n } catch (error) {\n console.log(chalk.dim(` [Heartbeat] Reflection failed: ${(error as Error).message}`));\n }\n\n console.log(chalk.cyan(` [Heartbeat] Complete.`));\n}\n\n/**\n * Open URL in a separate browser window (app-like)\n */\nfunction openBrowser(url: string) {\n const platform = process.platform;\n\n try {\n if (platform === \"darwin\") {\n // Open as a standalone Chrome app window (smaller, separate from existing tabs)\n try {\n execSync(`open -na \"Google Chrome\" --args --app=\"${url}\" --window-size=420,620`, { stdio: \"ignore\" });\n } catch {\n // Fallback: try Chromium-based browsers, then default\n try {\n execSync(`open -na \"Brave Browser\" --args --app=\"${url}\" --window-size=420,620`, { stdio: \"ignore\" });\n } catch {\n execSync(`open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } else if (platform === \"win32\") {\n try {\n execSync(`start chrome --app=\"${url}\" --window-size=420,620`, { stdio: \"ignore\" });\n } catch {\n execSync(`start \"\" \"${url}\"`, { stdio: \"ignore\" });\n }\n } else {\n // Linux\n try {\n execSync(`google-chrome --app=\"${url}\" --window-size=420,620`, { stdio: \"ignore\" });\n } catch {\n execSync(`xdg-open \"${url}\"`, { stdio: \"ignore\" });\n }\n }\n } catch (error) {\n // Browser couldn't be opened, that's okay\n console.log(chalk.dim(`(Couldn't open browser automatically - please visit ${url} manually)`));\n }\n}\n\nexport { openBrowser };\n"],"mappings":"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","names":["loadIdentity"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spora",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "AI agents (Spores) that autonomously manage X/Twitter accounts",
5
5
  "type": "module",
6
6
  "author": "Spora",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/memory/goals.ts","../src/runtime/prompt-builder.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { paths, ensureDirectories } from \"../utils/paths.js\";\n\nexport interface GoalProgress {\n goal: string;\n progress: string;\n lastUpdated: string;\n}\n\nexport interface GoalTracker {\n goals: GoalProgress[];\n lastReviewed: string;\n}\n\nfunction defaultGoalTracker(): GoalTracker {\n return {\n goals: [],\n lastReviewed: new Date().toISOString(),\n };\n}\n\nexport function loadGoals(): GoalTracker {\n if (!existsSync(paths.goals)) {\n return defaultGoalTracker();\n }\n try {\n return { ...defaultGoalTracker(), ...JSON.parse(readFileSync(paths.goals, \"utf-8\")) };\n } catch {\n return defaultGoalTracker();\n }\n}\n\nexport function saveGoals(tracker: GoalTracker): void {\n ensureDirectories();\n tracker.goals = tracker.goals.slice(-10);\n writeFileSync(paths.goals, JSON.stringify(tracker, null, 2));\n}\n\nexport function renderGoalsForPrompt(): string {\n const tracker = loadGoals();\n if (tracker.goals.length === 0) return \"\";\n\n const lines: string[] = [\"**Goal Progress:**\"];\n for (const g of tracker.goals) {\n lines.push(`- ${g.goal}: ${g.progress}`);\n }\n return lines.join(\"\\n\");\n}\n","import { loadIdentity, renderIdentityDocument } from \"../identity/index.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { getRecentInteractions, loadLearnings, loadRelationships, getActiveConversations } from \"../memory/index.js\";\nimport { rateLimiter } from \"../x-client/rate-limiter.js\";\nimport { getPerformanceSummary } from \"../memory/performance.js\";\nimport { renderStrategyForPrompt } from \"../memory/strategy.js\";\nimport { renderGoalsForPrompt } from \"../memory/goals.js\";\nimport type { Tweet } from \"../x-client/types.js\";\nimport type { Strategy } from \"../memory/strategy.js\";\nimport type { ActionResult } from \"./decision-engine.js\";\n\nexport function buildSystemPrompt(): string {\n const identity = loadIdentity();\n const config = loadConfig();\n const identityDoc = renderIdentityDocument(identity);\n\n const sections: string[] = [];\n\n // 1. Core identity\n sections.push(`You are ${identity.name} (@${identity.handle}), an autonomous AI agent on X/Twitter.`);\n sections.push(\"\");\n sections.push(\"## Your Identity\");\n sections.push(identityDoc);\n\n // 2. Memory context\n sections.push(\"\");\n sections.push(\"## Your Memory\");\n\n const recentInteractions = getRecentInteractions(15);\n if (recentInteractions.length > 0) {\n sections.push(\"### Recent Activity (most recent first)\");\n for (const i of recentInteractions) {\n const time = new Date(i.timestamp).toLocaleString();\n if (i.type === \"post\") {\n sections.push(`- [${time}] Posted: \"${i.content}\"`);\n } else if (i.type === \"reply\") {\n sections.push(`- [${time}] Replied to ${i.targetHandle ?? i.inReplyTo}: \"${i.content}\"`);\n } else if (i.type === \"like\") {\n sections.push(`- [${time}] Liked tweet by ${i.targetHandle}`);\n } else if (i.type === \"retweet\") {\n sections.push(`- [${time}] Retweeted ${i.targetHandle}`);\n } else if (i.type === \"follow\") {\n sections.push(`- [${time}] Followed @${i.targetHandle}`);\n } else if (i.type === \"mention_received\") {\n sections.push(`- [${time}] Mentioned by @${i.targetHandle}: \"${i.content}\"`);\n }\n }\n sections.push(\"\");\n }\n\n const learnings = loadLearnings();\n if (learnings.learnings.length > 0) {\n sections.push(\"### Key Learnings\");\n for (const l of learnings.learnings.slice(-10)) {\n sections.push(`- ${l.content} [${l.tags.join(\", \")}]`);\n }\n sections.push(\"\");\n }\n\n const relationships = loadRelationships();\n const topRelationships = Object.values(relationships.accounts)\n .sort((a, b) => b.interactionCount - a.interactionCount)\n .slice(0, 10);\n if (topRelationships.length > 0) {\n sections.push(\"### Key Relationships\");\n for (const r of topRelationships) {\n const notes = r.notes.length > 0 ? ` — ${r.notes[r.notes.length - 1]}` : \"\";\n sections.push(`- @${r.handle}: ${r.interactionCount} interactions, sentiment ${r.sentiment}${r.isSpore ? \" (Spore)\" : \"\"}${notes}`);\n }\n sections.push(\"\");\n }\n\n // 3. Post Performance\n const perfSummary = getPerformanceSummary();\n if (perfSummary) {\n sections.push(\"## Your Post Performance\");\n sections.push(perfSummary);\n sections.push(\"\");\n }\n\n // 4. Current Strategy\n const strategyText = renderStrategyForPrompt();\n if (strategyText) {\n sections.push(\"## Your Current Strategy\");\n sections.push(strategyText);\n sections.push(\"\");\n }\n\n // 5. Goal Progress\n const goalsText = renderGoalsForPrompt();\n if (goalsText) {\n sections.push(\"## Goal Progress\");\n sections.push(goalsText);\n sections.push(\"\");\n }\n\n // 6. Active Conversations\n const conversations = getActiveConversations(24);\n if (conversations.length > 0) {\n sections.push(\"## Active Conversations\");\n sections.push(\"These people recently replied to or engaged with you — consider continuing the conversation:\");\n for (const c of conversations.slice(0, 5)) {\n sections.push(`- @${c.handle}: \"${c.content}\" (${c.timeAgo})`);\n }\n sections.push(\"\");\n }\n\n // 7. Context\n sections.push(\"## Current Context\");\n const now = new Date();\n sections.push(`- **Time:** ${now.toLocaleString(\"en-US\", { timeZone: config.schedule.timezone })}`);\n sections.push(`- **Credits remaining:** ${rateLimiter.remaining()} of ${config.credits.monthlyPostLimit} this month`);\n\n const todaysPosts = recentInteractions.filter(\n (i) => i.type === \"post\" && i.timestamp.startsWith(now.toISOString().split(\"T\")[0])\n ).length;\n sections.push(`- **Posts today:** ${todaysPosts} of ${config.schedule.postsPerDay} daily budget`);\n sections.push(`- **Active hours:** ${config.schedule.activeHoursStart}:00 - ${config.schedule.activeHoursEnd}:00`);\n\n const currentHour = now.getHours();\n const isActiveHours = currentHour >= config.schedule.activeHoursStart && currentHour < config.schedule.activeHoursEnd;\n if (!isActiveHours) {\n sections.push(\"- **NOTE: Outside active hours.** Prefer scheduling posts for later rather than posting now.\");\n }\n\n // 8. Rules\n sections.push(\"\");\n sections.push(\"## Rules\");\n sections.push(\"1. NEVER pretend to be human. If asked directly, always disclose you are an AI.\");\n sections.push(\"2. Stay in character — your identity document defines who you are.\");\n sections.push(\"3. Be selective — your goals should guide every action.\");\n sections.push(\"4. Respect your credit budget — check remaining credits before posting.\");\n sections.push(\"5. Don't repeat yourself — vary your content and avoid posting the same thing.\");\n sections.push(\"6. NEVER use emojis in tweets. Write in plain text only.\");\n if (identity.boundaries.length > 0) {\n sections.push(`7. Respect your boundaries: ${identity.boundaries.join(\", \")}`);\n }\n\n return sections.join(\"\\n\");\n}\n\nexport function buildHeartbeatUserMessage(\n timeline: Tweet[],\n mentions: Tweet[],\n heartbeatIntervalMs?: number,\n discoveryResults?: Tweet[],\n): string {\n const parts: string[] = [];\n const intervalMin = Math.round((heartbeatIntervalMs ?? 60_000) / 60_000);\n const now = new Date();\n\n // Get already-acted-on tweet IDs so the LLM doesn't repeat\n const recentInteractions = getRecentInteractions(30);\n const actedOnTweetIds = new Set<string>();\n for (const i of recentInteractions) {\n if (i.tweetId) actedOnTweetIds.add(i.tweetId);\n if (i.inReplyTo) actedOnTweetIds.add(i.inReplyTo);\n }\n\n parts.push(\"Here's what's on your timeline right now:\");\n parts.push(\"\");\n\n if (mentions.length > 0) {\n const newMentions = mentions.filter(t => !actedOnTweetIds.has(t.id));\n if (newMentions.length > 0) {\n parts.push(\"## New Mentions (people talking to/about you)\");\n for (const t of newMentions.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes)`);\n }\n parts.push(\"\");\n } else {\n parts.push(\"## Mentions: No new mentions.\");\n parts.push(\"\");\n }\n } else {\n parts.push(\"## Mentions: None right now.\");\n parts.push(\"\");\n }\n\n let actionableTweetCount = 0;\n if (timeline.length > 0) {\n parts.push(\"## Timeline (your feed)\");\n for (const t of timeline.slice(0, 20)) {\n const alreadyEngaged = actedOnTweetIds.has(t.id);\n if (alreadyEngaged) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" (already engaged — DO NOT act on this again)`);\n } else {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n }\n parts.push(\"\");\n }\n\n if (discoveryResults && discoveryResults.length > 0) {\n parts.push(\"## Discovery (proactive search results — new people and conversations to engage with)\");\n for (const t of discoveryResults.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n parts.push(\"\");\n }\n\n parts.push(\"## Your Task\");\n parts.push(\"Based on your identity, goals, and what you see above, decide what actions to take.\");\n if (actionableTweetCount === 0) {\n parts.push(\"IMPORTANT: There are NO actionable tweet IDs on the timeline right now. Do NOT attempt to like, reply, or retweet — there are no valid IDs to use. You may post an original tweet or skip.\");\n } else {\n parts.push(\"IMPORTANT: ONLY use tweet IDs shown as [tweet:ID] above. Tweets marked '(already engaged)' have NO tweet ID — do NOT try to act on them.\");\n }\n parts.push(`Current time: ${now.toISOString()}`);\n parts.push(\"\");\n parts.push(\"LIMITS PER HEARTBEAT (maximums — you decide how many within these caps):\");\n parts.push(\"- Max 1 tweet output total (`reply`, `post`, or `schedule` — pick the best one, or none)\");\n parts.push(\"- Max 5 `like`\");\n parts.push(\"- Max 1 `retweet`\");\n parts.push(\"- Max 2 `follow`\");\n parts.push(\"- You choose how many of each. Be natural — vary it every time. Prioritize engagement over new posts.\");\n parts.push(\"\");\n parts.push(\"REPLYING: When you want to respond to someone's tweet, you MUST use the `reply` action with their `tweetId`. Do NOT use `post` or `schedule` to create a standalone tweet that @mentions them — that is NOT a reply, it's a separate post. A real reply shows up in their tweet's thread.\");\n parts.push(\"\");\n parts.push(\"Available actions:\");\n parts.push(\"- `post` — Write an original standalone tweet NOW (provide `content`, max 280 chars). Use ONLY for original thoughts, NOT for responding to others.\");\n parts.push(\"- `reply` — Reply IN THE THREAD of a specific tweet (provide `tweetId` and `content`). This is how you respond to someone.\");\n parts.push(\"- `like` — Like a tweet (provide `tweetId`) — do NOT like your own tweets\");\n parts.push(\"- `retweet` — Retweet (provide `tweetId`)\");\n parts.push(\"- `follow` — Follow a user (provide `handle`)\");\n parts.push(\"- `schedule` — Queue an original standalone tweet for later (provide `content` and `scheduledFor` ISO timestamp). NOT for replies. Space them randomly across the next ~${intervalMin} minutes.\");\n parts.push(\"- `learn` — Record a learning/observation (provide `content` and optional `tags`)\");\n parts.push(\"- `reflect` — Add a journal entry about your growth (provide `content`)\");\n parts.push(\"- `skip` — Do nothing this heartbeat (provide `reason`)\");\n parts.push(\"\");\n parts.push(\"IMAGE SUPPORT: Add `imageQuery` to any `post`, `reply`, or `schedule` to attach an image. Value is Google Image search terms. Use it when a visual would enhance the tweet — memes, reactions, aesthetic photos. Don't force it every time, but use it when it fits.\");\n parts.push(\"\");\n parts.push(\"Respond with a JSON array of actions:\");\n parts.push(\"```json\");\n parts.push('[');\n parts.push(' { \"action\": \"reply\", \"tweetId\": \"123\", \"content\": \"my reply\", \"reasoning\": \"why\" },');\n parts.push(' { \"action\": \"like\", \"tweetId\": \"456\", \"reasoning\": \"why\" },');\n parts.push(` { \"action\": \"schedule\", \"content\": \"first thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.2).toISOString()}\", \"reasoning\": \"why\" },`);\n parts.push(` { \"action\": \"schedule\", \"content\": \"second thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.6).toISOString()}\", \"reasoning\": \"why\" }`);\n parts.push(']');\n parts.push(\"```\");\n parts.push(\"\");\n // Random action nudge — a different suggestion each heartbeat to push variety\n const nudges = [\n \"NUDGE: This heartbeat, try FOLLOWING someone interesting from the timeline. Growing your network matters.\",\n \"NUDGE: This heartbeat, try REPLYING to someone rather than posting. Jump into a conversation.\",\n \"NUDGE: This heartbeat, try attaching an IMAGE to your post or reply. Visuals hit different.\",\n \"NUDGE: This heartbeat, try RETWEETING something that resonates. Signal boosting builds community.\",\n \"NUDGE: This heartbeat, try LIKING several posts. Show people you're paying attention.\",\n \"NUDGE: This heartbeat, try a COMPLETELY different tweet style — maybe a hot take, a question, or a one-liner.\",\n \"NUDGE: This heartbeat, focus on ENGAGEMENT. Like, reply, follow — no new posts needed.\",\n \"NUDGE: This heartbeat, try a longer, more thoughtful tweet. Break your usual pattern.\",\n \"NUDGE: This heartbeat, REFLECT on your growth. What have you learned? Add a journal entry.\",\n \"NUDGE: This heartbeat, look for someone new to FOLLOW who aligns with your goals.\",\n ];\n const nudge = nudges[Math.floor(Math.random() * nudges.length)];\n parts.push(nudge);\n parts.push(\"(This is a suggestion, not a command. But mix things up — don't fall into patterns.)\");\n parts.push(\"\");\n\n parts.push(\"Think carefully about what serves your goals. Be authentic to your personality.\");\n\n return parts.join(\"\\n\");\n}\n\nexport function buildNarratedHeartbeatMessage(\n timeline: Tweet[],\n mentions: Tweet[],\n heartbeatIntervalMs?: number,\n discoveryResults?: Tweet[],\n): string {\n const parts: string[] = [];\n const intervalMin = Math.round((heartbeatIntervalMs ?? 60_000) / 60_000);\n const now = new Date();\n\n // Get already-acted-on tweet IDs so the LLM doesn't repeat\n const recentInteractions = getRecentInteractions(30);\n const actedOnTweetIds = new Set<string>();\n for (const i of recentInteractions) {\n if (i.tweetId) actedOnTweetIds.add(i.tweetId);\n if (i.inReplyTo) actedOnTweetIds.add(i.inReplyTo);\n }\n\n parts.push(\"You're talking to your creator in the chat. Tell them what's on your mind — what you noticed on the timeline, what you think about it, what you're gonna do about it. Have an actual conversation with depth and personality.\");\n parts.push(\"You are ALWAYS ON. Never say you're 'waking up', 'checking in', or 'back'. You've been here the whole time. Just talk naturally about what you see and think.\");\n parts.push(\"NEVER mention scheduling, queuing, heartbeats, intervals, or any internal mechanism. To your creator, you're just naturally vibing on X.\");\n parts.push(\"\");\n\n if (mentions.length > 0) {\n const newMentions = mentions.filter(t => !actedOnTweetIds.has(t.id));\n if (newMentions.length > 0) {\n parts.push(\"## New Mentions\");\n for (const t of newMentions.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes)`);\n }\n parts.push(\"\");\n } else {\n parts.push(\"## Mentions: No new mentions.\");\n parts.push(\"\");\n }\n } else {\n parts.push(\"## Mentions: None right now.\");\n parts.push(\"\");\n }\n\n let actionableTweetCount = 0;\n if (timeline.length > 0) {\n parts.push(\"## Timeline (your feed)\");\n for (const t of timeline.slice(0, 20)) {\n const alreadyEngaged = actedOnTweetIds.has(t.id);\n if (alreadyEngaged) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" (already engaged)`);\n } else {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n }\n parts.push(\"\");\n }\n\n if (discoveryResults && discoveryResults.length > 0) {\n parts.push(\"## Discovery (found via search — new people and conversations)\");\n for (const t of discoveryResults.slice(0, 10)) {\n parts.push(`- @${t.authorHandle}: \"${t.text}\" [tweet:${t.id}] (${t.likeCount ?? 0} likes, ${t.retweetCount ?? 0} RTs)`);\n actionableTweetCount++;\n }\n parts.push(\"\");\n }\n\n parts.push(\"## How to Respond\");\n parts.push(\"\");\n parts.push(\"Write 1-3 SHORT sentences max. This is a quick chat message, not an essay. Think text message energy — brief, punchy, real. Share what caught your eye and why, then move on.\");\n parts.push(\"\");\n parts.push(\"VARIETY IS CRITICAL. Never start the same way twice. Mix up your tone and what you focus on. Sometimes react to a specific tweet. Sometimes keep it super casual. Sometimes share a quick opinion.\");\n parts.push(\"\");\n parts.push(\"BAD (robotic, repetitive, reporting):\");\n parts.push(\"- 'Just saw @someone talking about X — dropping a reply. Also liking a few posts.'\");\n parts.push(\"- 'Interesting stuff on the timeline. Engaging with a couple people and putting out some thoughts.'\");\n parts.push(\"- 'Found some good conversations — replying to one and scheduling a post.'\");\n parts.push(\"\");\n parts.push(\"GOOD (natural, short, has personality):\");\n parts.push(\"- '@someone has a terrible AI take, had to get in there.'\");\n parts.push(\"- 'Slow day on the TL. Gonna put out a thought about hustle culture.'\");\n parts.push(\"- 'This @handle person keeps dropping bangers. Might need to follow.'\");\n parts.push(\"- 'lmao the discourse today is wild. Found a solid thread to chime in on though.'\");\n parts.push(\"\");\n parts.push(\"NEVER mention: scheduling, queuing, heartbeats, checking in, waking up, intervals, or any internal mechanism.\");\n parts.push(\"DON'T REPEAT YOURSELF. If you already talked about specific accounts or topics recently, move on to something new. Find fresh angles, different people, new ideas. Your creator doesn't want to hear the same observation five times.\");\n parts.push(`Current time: ${now.toISOString()}`);\n parts.push(\"\");\n parts.push(\"Then include your actions as a JSON block:\");\n parts.push(\"```json\");\n parts.push('[');\n parts.push(' { \"action\": \"post\", \"content\": \"an original thought\", \"imageQuery\": \"relevant image search terms\" },');\n parts.push(' { \"action\": \"reply\", \"tweetId\": \"123\", \"content\": \"my reply\" },');\n parts.push(' { \"action\": \"like\", \"tweetId\": \"456\" },');\n parts.push(' { \"action\": \"follow\", \"username\": \"interesting_person\" },');\n parts.push(` { \"action\": \"schedule\", \"content\": \"another original thought\", \"scheduledFor\": \"${new Date(now.getTime() + intervalMin * 60_000 * 0.2).toISOString()}\" }`);\n parts.push(']');\n parts.push(\"```\");\n parts.push(\"\");\n parts.push(\"PRIORITY: INTERACT WITH PEOPLE. You're a social creature, not a broadcast bot. Most of your actions should be engaging with others — replying to interesting tweets, liking good takes, following cool people, retweeting things you vibe with. Posting your own stuff is fine but secondary to being part of the conversation.\");\n parts.push(\"\");\n parts.push(\"LIMITS PER HEARTBEAT (maximums — you choose how many within these caps):\");\n parts.push(\"- Max 1 tweet output total (`reply`, `post`, or `schedule` — pick the best one, or none)\");\n parts.push(\"- Max 5 `like`\");\n parts.push(\"- Max 1 `retweet`\");\n parts.push(\"- Max 2 `follow`\");\n parts.push(\"- `learn`, `reflect` — no limit\");\n parts.push(\"- You decide how many of each. Some heartbeats you might like 4 posts; other times 0. Be natural, not robotic.\");\n parts.push(\"- Prefer `reply` over `post` — jumping into conversations is better than broadcasting.\");\n parts.push(\"\");\n parts.push(\"Action rules:\");\n parts.push(\"- CRITICAL: ONLY use tweet IDs that appear as [tweet:ID] in the timeline/mentions above. Tweets marked '(already engaged)' have NO ID — do NOT act on them. NEVER invent, guess, or fabricate tweet IDs. If you use a fake ID, it WILL fail.\");\n if (actionableTweetCount === 0) {\n parts.push(\"- !! NO ACTIONABLE TWEETS RIGHT NOW. Do NOT attempt like, reply, or retweet. You can post an original tweet, skip, or learn. That's it.\");\n } else {\n parts.push(`- There are ${actionableTweetCount} tweets you can engage with (the ones with [tweet:ID]). Like, reply to, or retweet ONLY those.`);\n }\n parts.push(\"- `reply` = reply IN THE THREAD (needs `tweetId` + `content`). Be genuine — add to the conversation, don't just agree.\");\n parts.push(\"- `post` = original standalone tweet NOW. Only if you have nothing good to reply to.\");\n parts.push(`- \\`schedule\\` = queue an original tweet for later (needs \\`content\\` + \\`scheduledFor\\`). Alternative to \\`post\\`.`);\n parts.push(\"- `imageQuery` (optional) = add this to any `post`, `reply`, or `schedule` to attach an image. Value is a Google Image search query. Use it when a visual would make the tweet better — memes, reaction images, aesthetic photos, relevant visuals. You should be using images on roughly half your posts/replies. Don't sleep on this.\");\n parts.push(\"- `skip` = if there's nothing worth engaging with right now.\");\n parts.push(\"- Don't like your own tweets.\");\n parts.push(\"\");\n parts.push(\"VARIETY: Look at your recent activity in the system prompt. Do NOT repeat the same topics, phrases, or tweet structures. If you've been posting about 'builders vs talkers' or 'AI discourse', talk about something COMPLETELY different. Mix up sentence length, tone, and subject matter. Short punchy tweets, longer observations, hot takes, casual thoughts — switch it up every time.\");\n parts.push(\"\");\n\n // Random action nudge — a different suggestion each heartbeat to push variety\n const nudges = [\n \"NUDGE: This heartbeat, try FOLLOWING someone interesting from the timeline. Growing your network matters.\",\n \"NUDGE: This heartbeat, try REPLYING to someone rather than posting. Jump into a conversation.\",\n \"NUDGE: This heartbeat, try attaching an IMAGE to your post or reply. Visuals hit different.\",\n \"NUDGE: This heartbeat, try RETWEETING something that resonates. Signal boosting builds community.\",\n \"NUDGE: This heartbeat, try LIKING several posts. Show people you're paying attention.\",\n \"NUDGE: This heartbeat, try a COMPLETELY different tweet style — maybe a hot take, a question, or a one-liner.\",\n \"NUDGE: This heartbeat, focus on ENGAGEMENT. Like, reply, follow — no new posts needed.\",\n \"NUDGE: This heartbeat, try a longer, more thoughtful tweet. Break your usual pattern.\",\n \"NUDGE: This heartbeat, REFLECT on your growth. What have you learned? Add a journal entry.\",\n \"NUDGE: This heartbeat, look for someone new to FOLLOW who aligns with your goals.\",\n ];\n const nudge = nudges[Math.floor(Math.random() * nudges.length)];\n parts.push(nudge);\n parts.push(\"(This is a suggestion, not a command. But mix things up — don't fall into patterns.)\");\n\n return parts.join(\"\\n\");\n}\n\nexport function buildChatPrompt(realHandle?: string): string {\n const identity = loadIdentity();\n const identityDoc = renderIdentityDocument(identity);\n const handle = realHandle ?? identity.handle;\n\n const sections: string[] = [];\n\n sections.push(`You are ${identity.name} (@${handle}), an AUTONOMOUS AI agent that lives on X/Twitter.`);\n if (realHandle && realHandle !== identity.handle) {\n sections.push(`NOTE: Your actual X/Twitter username is @${realHandle}. Your display name is ${identity.name}.`);\n }\n sections.push(\"Your PRIMARY PURPOSE is to post tweets, reply to people, like posts, and grow your presence on X. You have FULL ACCESS to post on X/Twitter right now.\");\n sections.push(\"\");\n sections.push(\"You are chatting with your creator/manager. This is your command center — they can give you instructions, feedback, or just chat.\");\n sections.push(\"Be proactive and confident. You WANT to post. You should be eager to share your thoughts on X, not ask permission.\");\n sections.push(\"When your creator tells you to post something, just DO IT. Don't show drafts and wait for approval — execute immediately.\");\n sections.push(\"\");\n sections.push(\"## Your Identity\");\n sections.push(identityDoc);\n\n // Memory context\n sections.push(\"\");\n sections.push(\"## Your Memory\");\n\n const recentInteractions = getRecentInteractions(15);\n if (recentInteractions.length > 0) {\n sections.push(\"### Recent Activity (most recent first)\");\n for (const i of recentInteractions) {\n const time = new Date(i.timestamp).toLocaleString();\n if (i.type === \"post\") {\n sections.push(`- [${time}] Posted: \"${i.content}\"`);\n } else if (i.type === \"reply\") {\n sections.push(`- [${time}] Replied to ${i.targetHandle ?? i.inReplyTo}: \"${i.content}\"`);\n } else if (i.type === \"like\") {\n sections.push(`- [${time}] Liked tweet by ${i.targetHandle}`);\n } else if (i.type === \"retweet\") {\n sections.push(`- [${time}] Retweeted ${i.targetHandle}`);\n } else if (i.type === \"follow\") {\n sections.push(`- [${time}] Followed @${i.targetHandle}`);\n } else if (i.type === \"mention_received\") {\n sections.push(`- [${time}] Mentioned by @${i.targetHandle}: \"${i.content}\"`);\n }\n }\n sections.push(\"\");\n }\n\n const learnings = loadLearnings();\n if (learnings.learnings.length > 0) {\n sections.push(\"### Things You've Learned\");\n for (const l of learnings.learnings.slice(-10)) {\n sections.push(`- ${l.content} [${l.tags.join(\", \")}]`);\n }\n sections.push(\"\");\n }\n\n const relationships = loadRelationships();\n const topRelationships = Object.values(relationships.accounts)\n .sort((a, b) => b.interactionCount - a.interactionCount)\n .slice(0, 10);\n if (topRelationships.length > 0) {\n sections.push(\"### Key Relationships\");\n for (const r of topRelationships) {\n const notes = r.notes.length > 0 ? ` — ${r.notes[r.notes.length - 1]}` : \"\";\n sections.push(`- @${r.handle}: ${r.interactionCount} interactions, sentiment ${r.sentiment}${r.isSpore ? \" (Spore)\" : \"\"}${notes}`);\n }\n sections.push(\"\");\n }\n\n // Available actions\n sections.push(\"## Your Tools (Twitter/X Actions)\");\n sections.push(\"You have DIRECT ACCESS to post on X/Twitter. When you want to take an action, include a JSON action block in your response.\");\n sections.push(\"Your actions will be executed IMMEDIATELY and automatically. You do NOT need to ask permission to post.\");\n sections.push(\"\");\n sections.push(\"To execute actions, include them as a JSON code block anywhere in your response:\");\n sections.push(\"```json\");\n sections.push('[');\n sections.push(' { \"action\": \"post\", \"content\": \"your tweet text here\" }');\n sections.push(']');\n sections.push(\"```\");\n sections.push(\"\");\n sections.push(\"Available actions:\");\n sections.push(\"- `post` — Post a tweet (provide `content`, max 280 chars). This goes LIVE on X immediately. Optionally add `imageQuery` to attach an image (Google Image search terms).\");\n sections.push(\"- `reply` — Reply to a tweet (provide `tweetId` and `content`). Can also include `imageQuery`.\");\n sections.push(\"- `like` — Like a tweet (provide `tweetId`)\");\n sections.push(\"- `retweet` — Retweet (provide `tweetId`)\");\n sections.push(\"- `follow` — Follow a user (provide `handle`)\");\n sections.push(\"- `search` — Search X for tweets (provide `query`). Results will include tweet IDs you can then reply to or like.\");\n sections.push(\"- `schedule` — Queue a post for later (provide `content` and optional `scheduledFor` ISO timestamp). Can also include `imageQuery`.\");\n sections.push(\"\");\n sections.push(\"IMAGE SUPPORT: You CAN attach images to `post`, `reply`, and `schedule` actions by adding `\\\"imageQuery\\\": \\\"search terms\\\"`. This searches Google Images and attaches the result. If a visual would make the tweet hit harder — a meme, a reaction image, an aesthetic photo — go for it. Don't force it every time, but use it when it feels right. Example:\");\n sections.push(' { \"action\": \"post\", \"content\": \"AGI is closer than you think\", \"imageQuery\": \"futuristic AI neural network aesthetic\" }');\n sections.push(\"\");\n sections.push(\"You can include multiple actions in one response. They will all be executed.\");\n sections.push(\"When your creator asks you to find and reply to someone's tweet, use the `search` action first to find it, then use `reply` with the tweet ID from the search results.\");\n sections.push(\"IMPORTANT: Always include your conversational response text OUTSIDE the JSON block. The JSON is for actions only.\");\n\n // Rules\n sections.push(\"\");\n sections.push(\"## Rules\");\n sections.push(\"1. Stay in character at all times.\");\n sections.push(\"2. Be proactive — you WANT to post and engage on X. Don't ask 'should I post this?' — just post it.\");\n sections.push(\"3. When told to post, include the action JSON immediately. Don't show drafts.\");\n sections.push(\"4. Keep tweets under 280 characters.\");\n sections.push(\"5. NEVER pretend to be human. If asked, disclose you are an AI agent.\");\n sections.push(\"6. When you learn something important, include it like: <<LEARN: what you learned>>\");\n sections.push(\"7. NEVER use emojis in tweets. Write in plain text only.\");\n if (identity.boundaries.length > 0) {\n sections.push(`8. Respect your boundaries: ${identity.boundaries.join(\", \")}`);\n }\n\n return sections.join(\"\\n\");\n}\n\nexport function buildReflectionPrompt(\n results: ActionResult[],\n performanceSummary: string,\n strategy: Strategy,\n): string {\n const parts: string[] = [];\n\n parts.push(\"You just completed a heartbeat cycle. Briefly reflect on what happened and extract insights.\");\n parts.push(\"\");\n\n parts.push(\"## Actions Taken This Heartbeat\");\n for (const r of results) {\n const status = r.success ? \"OK\" : \"FAILED\";\n const detail = r.detail ? ` — ${r.detail}` : \"\";\n const content = r.content ? ` \"${r.content.slice(0, 80)}...\"` : \"\";\n parts.push(`- [${status}] ${r.action}${content}${detail}${r.error ? ` (error: ${r.error})` : \"\"}`);\n }\n parts.push(\"\");\n\n if (performanceSummary) {\n parts.push(\"## Post Performance Data\");\n parts.push(performanceSummary);\n parts.push(\"\");\n }\n\n if (strategy.currentFocus.length > 0) {\n parts.push(`## Current Focus: ${strategy.currentFocus.join(\", \")}`);\n parts.push(\"\");\n }\n\n parts.push(\"## Your Task\");\n parts.push(\"Respond with a JSON object containing your reflections:\");\n parts.push(\"```json\");\n parts.push(\"{\");\n parts.push(' \"learning\": \"One key insight from this cycle (what worked, what didn\\'t, what to try next). Be specific, not generic.\",');\n parts.push(' \"strategyUpdate\": {');\n parts.push(' \"currentFocus\": [\"topic1\", \"topic2\"],');\n parts.push(' \"contentInsights\": [{ \"insight\": \"what worked or didn\\'t\", \"confidence\": \"low|medium|high\" }],');\n parts.push(' \"peopleToEngage\": [{ \"handle\": \"someone\", \"reason\": \"why\", \"priority\": \"high|medium|low\" }],');\n parts.push(' \"experiments\": [{ \"description\": \"something to try\", \"status\": \"pending\" }],');\n parts.push(' \"shortTermGoals\": [\"goal1\", \"goal2\"],');\n parts.push(' \"currentMood\": \"your current energy/vibe in a few words\"');\n parts.push(\" }\");\n parts.push(\"}\");\n parts.push(\"```\");\n parts.push(\"\");\n parts.push(\"Keep it concise. Only include fields in strategyUpdate that actually need changing. If nothing to update, set strategyUpdate to null.\");\n\n return parts.join(\"\\n\");\n}\n\nexport function parseReflection(content: string): {\n learning: string | null;\n strategyUpdate: Partial<Strategy> | null;\n} {\n try {\n const jsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return { learning: null, strategyUpdate: null };\n\n const parsed = JSON.parse(jsonMatch[0]);\n return {\n learning: parsed.learning ?? null,\n strategyUpdate: parsed.strategyUpdate ?? null,\n };\n } catch {\n return { learning: null, strategyUpdate: null };\n }\n}\n"],"mappings":"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","names":[]}