spora 0.7.3 → 0.7.4

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.
Files changed (32) hide show
  1. package/dist/{autonomy-6UWPXTPD.js → autonomy-XUKCAZM3.js} +4 -4
  2. package/dist/{chunk-ZBP2ROAZ.js → chunk-EU4FMOKG.js} +3 -3
  3. package/dist/{chunk-7OOGNZBU.js → chunk-MDOFAAZB.js} +2 -2
  4. package/dist/{chunk-HGNMHGAF.js → chunk-PN5A6MCV.js} +2 -2
  5. package/dist/{chunk-VZBHRUZS.js → chunk-QWEYVDLU.js} +1 -1
  6. package/dist/chunk-QWEYVDLU.js.map +1 -0
  7. package/dist/cli.js +26 -26
  8. package/dist/{client-4KGOBIXE.js → client-Z5UQWPPI.js} +2 -2
  9. package/dist/{colony-IVYR233C.js → colony-NNX45EAV.js} +2 -2
  10. package/dist/{crypto-NOXNL4GP.js → crypto-B65ZH7KN.js} +2 -2
  11. package/dist/{heartbeat-CUL2FTFD.js → heartbeat-ZCCOIZGU.js} +4 -4
  12. package/dist/{init-2REECUVH.js → init-SEJPTOOB.js} +4 -4
  13. package/dist/mcp-server.js +19 -19
  14. package/dist/{queue-D3MRKABU.js → queue-2ZBKDFX3.js} +2 -2
  15. package/dist/web-chat/chat.html +190 -3
  16. package/dist/{web-chat-O24HGJVE.js → web-chat-ZZ65DUID.js} +106 -8
  17. package/dist/web-chat-ZZ65DUID.js.map +1 -0
  18. package/dist/{x-client-ASXVQ6EV.js → x-client-YG7UCCNI.js} +2 -2
  19. package/package.json +1 -1
  20. package/dist/chunk-VZBHRUZS.js.map +0 -1
  21. package/dist/web-chat-O24HGJVE.js.map +0 -1
  22. /package/dist/{autonomy-6UWPXTPD.js.map → autonomy-XUKCAZM3.js.map} +0 -0
  23. /package/dist/{chunk-ZBP2ROAZ.js.map → chunk-EU4FMOKG.js.map} +0 -0
  24. /package/dist/{chunk-7OOGNZBU.js.map → chunk-MDOFAAZB.js.map} +0 -0
  25. /package/dist/{chunk-HGNMHGAF.js.map → chunk-PN5A6MCV.js.map} +0 -0
  26. /package/dist/{client-4KGOBIXE.js.map → client-Z5UQWPPI.js.map} +0 -0
  27. /package/dist/{colony-IVYR233C.js.map → colony-NNX45EAV.js.map} +0 -0
  28. /package/dist/{crypto-NOXNL4GP.js.map → crypto-B65ZH7KN.js.map} +0 -0
  29. /package/dist/{heartbeat-CUL2FTFD.js.map → heartbeat-ZCCOIZGU.js.map} +0 -0
  30. /package/dist/{init-2REECUVH.js.map → init-SEJPTOOB.js.map} +0 -0
  31. /package/dist/{queue-D3MRKABU.js.map → queue-2ZBKDFX3.js.map} +0 -0
  32. /package/dist/{x-client-ASXVQ6EV.js.map → x-client-YG7UCCNI.js.map} +0 -0
@@ -38,7 +38,7 @@
38
38
  margin-left: auto;
39
39
  display: flex;
40
40
  align-items: center;
41
- gap: 0.5rem;
41
+ gap: 0.75rem;
42
42
  }
43
43
 
44
44
  .status-dot {
@@ -54,6 +54,82 @@
54
54
  color: rgba(255, 255, 255, 0.4);
55
55
  }
56
56
 
57
+ .heartbeat-menu {
58
+ position: relative;
59
+ }
60
+
61
+ .heartbeat-trigger {
62
+ width: 30px;
63
+ height: 30px;
64
+ border-radius: 999px;
65
+ border: 1px solid rgba(255, 255, 255, 0.15);
66
+ background: #141414;
67
+ color: #ff647d;
68
+ cursor: pointer;
69
+ font-size: 0.95rem;
70
+ line-height: 1;
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ transition: transform 0.15s ease, border-color 0.2s ease;
75
+ }
76
+
77
+ .heartbeat-trigger:hover {
78
+ transform: scale(1.06);
79
+ border-color: rgba(255, 100, 125, 0.45);
80
+ }
81
+
82
+ .heartbeat-trigger:disabled {
83
+ opacity: 0.4;
84
+ cursor: not-allowed;
85
+ }
86
+
87
+ .heartbeat-popover {
88
+ position: absolute;
89
+ right: 0;
90
+ top: calc(100% + 0.45rem);
91
+ min-width: 150px;
92
+ background: #111111;
93
+ border: 1px solid rgba(255, 255, 255, 0.14);
94
+ border-radius: 0.7rem;
95
+ padding: 0.5rem;
96
+ z-index: 10;
97
+ box-shadow: 0 8px 18px rgba(0, 0, 0, 0.45);
98
+ animation: fadeIn 0.18s ease-in;
99
+ }
100
+
101
+ .heartbeat-popover-title {
102
+ font-size: 0.67rem;
103
+ color: rgba(255, 255, 255, 0.44);
104
+ margin-bottom: 0.35rem;
105
+ padding: 0 0.2rem;
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.04em;
108
+ }
109
+
110
+ .heartbeat-option {
111
+ width: 100%;
112
+ border: 1px solid transparent;
113
+ background: transparent;
114
+ color: rgba(255, 255, 255, 0.88);
115
+ padding: 0.42rem 0.5rem;
116
+ font-size: 0.75rem;
117
+ text-align: left;
118
+ border-radius: 0.5rem;
119
+ cursor: pointer;
120
+ transition: background 0.18s ease, border-color 0.18s ease;
121
+ }
122
+
123
+ .heartbeat-option:hover {
124
+ background: rgba(255, 255, 255, 0.06);
125
+ }
126
+
127
+ .heartbeat-option.active {
128
+ border-color: rgba(56, 158, 119, 0.65);
129
+ background: rgba(56, 158, 119, 0.16);
130
+ color: #89dbba;
131
+ }
132
+
57
133
  @keyframes pulse {
58
134
  0%, 100% { opacity: 1; }
59
135
  50% { opacity: 0.5; }
@@ -328,7 +404,18 @@
328
404
  </svg>
329
405
  <div class="header-right">
330
406
  <span class="status-dot"></span>
331
- <span class="status-text">Online</span>
407
+ <span class="status-text" id="statusText">Online</span>
408
+ <div class="heartbeat-menu" id="heartbeatMenu">
409
+ <button class="heartbeat-trigger" id="heartbeatTrigger" title="Heartbeat settings" aria-label="Heartbeat settings">♥</button>
410
+ <div class="heartbeat-popover" id="heartbeatPopover" hidden>
411
+ <div class="heartbeat-popover-title">Heartbeat</div>
412
+ <button class="heartbeat-option" data-interval="60000">1 min</button>
413
+ <button class="heartbeat-option" data-interval="300000">5 min</button>
414
+ <button class="heartbeat-option" data-interval="1800000">30 min</button>
415
+ <button class="heartbeat-option" data-interval="3600000">1 hour</button>
416
+ <button class="heartbeat-option" data-interval="21600000">6 hours</button>
417
+ </div>
418
+ </div>
332
419
  </div>
333
420
  </div>
334
421
 
@@ -350,12 +437,86 @@
350
437
  const chatContainer = document.getElementById('chatContainer');
351
438
  const messageInput = document.getElementById('messageInput');
352
439
  const sendButton = document.getElementById('sendButton');
440
+ const heartbeatMenu = document.getElementById('heartbeatMenu');
441
+ const heartbeatTrigger = document.getElementById('heartbeatTrigger');
442
+ const heartbeatPopover = document.getElementById('heartbeatPopover');
443
+ const heartbeatOptions = Array.from(document.querySelectorAll('.heartbeat-option'));
444
+ const statusText = document.getElementById('statusText');
353
445
 
354
446
  let isLoading = false;
355
447
  let agentName = 'Your Spore';
356
448
  let agentHandle = '';
357
449
  let agentPfp = null;
358
450
  let sleepTimerId = null;
451
+ let heartbeatMs = 300000;
452
+
453
+ const HEARTBEAT_LABELS = {
454
+ 60000: '1 min',
455
+ 300000: '5 min',
456
+ 1800000: '30 min',
457
+ 3600000: '1 hour',
458
+ 21600000: '6 hours',
459
+ };
460
+
461
+ function heartbeatLabel(ms) {
462
+ return HEARTBEAT_LABELS[ms] || `${Math.round(ms / 60000)} min`;
463
+ }
464
+
465
+ function updateStatusText() {
466
+ statusText.textContent = `Online • ${heartbeatLabel(heartbeatMs)} heartbeat`;
467
+ }
468
+
469
+ function setHeartbeatPopoverOpen(isOpen) {
470
+ heartbeatPopover.hidden = !isOpen;
471
+ }
472
+
473
+ function updateHeartbeatOptions() {
474
+ for (const option of heartbeatOptions) {
475
+ const value = parseInt(option.dataset.interval || '0', 10);
476
+ option.classList.toggle('active', value === heartbeatMs);
477
+ }
478
+ }
479
+
480
+ async function loadHeartbeatConfig() {
481
+ try {
482
+ const response = await fetch('/api/heartbeat-config');
483
+ if (!response.ok) return;
484
+ const data = await response.json();
485
+ if (data.intervalMs) {
486
+ heartbeatMs = data.intervalMs;
487
+ updateStatusText();
488
+ updateHeartbeatOptions();
489
+ }
490
+ } catch {
491
+ // best effort
492
+ }
493
+ }
494
+
495
+ async function setHeartbeatConfig(intervalMs) {
496
+ const previous = heartbeatMs;
497
+ heartbeatTrigger.disabled = true;
498
+ setHeartbeatPopoverOpen(false);
499
+ try {
500
+ const response = await fetch('/api/heartbeat-config', {
501
+ method: 'POST',
502
+ headers: { 'Content-Type': 'application/json' },
503
+ body: JSON.stringify({ intervalMs }),
504
+ });
505
+ if (!response.ok) {
506
+ throw new Error('Failed to update heartbeat');
507
+ }
508
+ heartbeatMs = intervalMs;
509
+ updateStatusText();
510
+ updateHeartbeatOptions();
511
+ addNarration(`Heartbeat updated to ${heartbeatLabel(intervalMs)}`, 'action');
512
+ } catch {
513
+ heartbeatMs = previous;
514
+ updateHeartbeatOptions();
515
+ addNarration('Could not update heartbeat interval', 'error');
516
+ } finally {
517
+ heartbeatTrigger.disabled = false;
518
+ }
519
+ }
359
520
 
360
521
  // ===== Init =====
361
522
  async function init() {
@@ -372,6 +533,7 @@
372
533
  console.error('Failed to load identity:', error);
373
534
  }
374
535
 
536
+ await loadHeartbeatConfig();
375
537
  addMessage('assistant', `Hey! I'm ${agentName}. I run on my own — posting, replying, exploring X during my heartbeat cycles. You can shape who I am by chatting with me here.`);
376
538
  await loadMessages();
377
539
  }
@@ -480,7 +642,6 @@
480
642
 
481
643
  // ===== Narration =====
482
644
  function addNarration(text, type) {
483
- removeSleepCard();
484
645
  const el = document.createElement('div');
485
646
  el.className = 'narration';
486
647
  const dot = document.createElement('div');
@@ -588,6 +749,30 @@
588
749
  }
589
750
 
590
751
  sendButton.addEventListener('click', sendMessage);
752
+ heartbeatTrigger.addEventListener('click', (e) => {
753
+ e.preventDefault();
754
+ e.stopPropagation();
755
+ setHeartbeatPopoverOpen(heartbeatPopover.hidden);
756
+ });
757
+
758
+ heartbeatPopover.addEventListener('click', (e) => {
759
+ const target = e.target.closest('.heartbeat-option');
760
+ if (!target) return;
761
+ const intervalMs = parseInt(target.dataset.interval || '0', 10);
762
+ if (!Number.isFinite(intervalMs)) return;
763
+ if (intervalMs === heartbeatMs) {
764
+ setHeartbeatPopoverOpen(false);
765
+ return;
766
+ }
767
+ setHeartbeatConfig(intervalMs);
768
+ });
769
+
770
+ document.addEventListener('click', (e) => {
771
+ if (!heartbeatMenu.contains(e.target)) {
772
+ setHeartbeatPopoverOpen(false);
773
+ }
774
+ });
775
+
591
776
  messageInput.addEventListener('keydown', (e) => {
592
777
  if (e.key === 'Enter' && !e.shiftKey) {
593
778
  e.preventDefault();
@@ -642,6 +827,8 @@
642
827
  setInterval(pollAgentEvents, 2000);
643
828
 
644
829
  // Initialize
830
+ updateStatusText();
831
+ updateHeartbeatOptions();
645
832
  init();
646
833
  messageInput.focus();
647
834
  </script>
@@ -11,6 +11,7 @@ import { join, dirname } from "path";
11
11
  import { fileURLToPath } from "url";
12
12
  var __filename = fileURLToPath(import.meta.url);
13
13
  var __dirname = dirname(__filename);
14
+ var ALLOWED_HEARTBEAT_INTERVALS = [6e4, 3e5, 18e5, 36e5, 216e5];
14
15
  var WebChatServer = class {
15
16
  server = null;
16
17
  port;
@@ -19,6 +20,7 @@ var WebChatServer = class {
19
20
  agentEvents = [];
20
21
  onUserMessage;
21
22
  identity;
23
+ heartbeatConfigHandlers;
22
24
  constructor(port = 3737) {
23
25
  this.port = port;
24
26
  }
@@ -28,6 +30,9 @@ var WebChatServer = class {
28
30
  setMessageHandler(handler) {
29
31
  this.onUserMessage = handler;
30
32
  }
33
+ setHeartbeatConfigHandlers(handlers) {
34
+ this.heartbeatConfigHandlers = handlers;
35
+ }
31
36
  /** Push an activity update (from heartbeat) to the UI — kept for backwards compat */
32
37
  pushActivity(type, message) {
33
38
  this.activities.push({ type, message, timestamp: Date.now() });
@@ -109,6 +114,52 @@ var WebChatServer = class {
109
114
  res.end(JSON.stringify({ events: newEvents }));
110
115
  return;
111
116
  }
117
+ if (url.pathname === "/api/heartbeat-config" && req.method === "GET") {
118
+ if (!this.heartbeatConfigHandlers) {
119
+ res.writeHead(503, { "Content-Type": "application/json" });
120
+ res.end(JSON.stringify({ error: "Heartbeat config unavailable" }));
121
+ return;
122
+ }
123
+ try {
124
+ const intervalMs = await this.heartbeatConfigHandlers.getIntervalMs();
125
+ res.writeHead(200, { "Content-Type": "application/json" });
126
+ res.end(JSON.stringify({ intervalMs, allowedIntervals: ALLOWED_HEARTBEAT_INTERVALS }));
127
+ } catch {
128
+ res.writeHead(500, { "Content-Type": "application/json" });
129
+ res.end(JSON.stringify({ error: "Failed to load heartbeat config" }));
130
+ }
131
+ return;
132
+ }
133
+ if (url.pathname === "/api/heartbeat-config" && req.method === "POST") {
134
+ if (!this.heartbeatConfigHandlers) {
135
+ res.writeHead(503, { "Content-Type": "application/json" });
136
+ res.end(JSON.stringify({ error: "Heartbeat config unavailable" }));
137
+ return;
138
+ }
139
+ const handlers = this.heartbeatConfigHandlers;
140
+ let body = "";
141
+ req.on("data", (chunk) => {
142
+ body += chunk.toString();
143
+ });
144
+ req.on("end", async () => {
145
+ try {
146
+ const parsed = JSON.parse(body);
147
+ const intervalMs = parsed.intervalMs;
148
+ if (typeof intervalMs !== "number" || !Number.isInteger(intervalMs) || !ALLOWED_HEARTBEAT_INTERVALS.includes(intervalMs)) {
149
+ res.writeHead(400, { "Content-Type": "application/json" });
150
+ res.end(JSON.stringify({ error: "Unsupported heartbeat interval" }));
151
+ return;
152
+ }
153
+ await handlers.setIntervalMs(intervalMs);
154
+ res.writeHead(200, { "Content-Type": "application/json" });
155
+ res.end(JSON.stringify({ success: true, intervalMs }));
156
+ } catch {
157
+ res.writeHead(400, { "Content-Type": "application/json" });
158
+ res.end(JSON.stringify({ error: "Invalid request" }));
159
+ }
160
+ });
161
+ return;
162
+ }
112
163
  if (url.pathname === "/api/message" && req.method === "POST") {
113
164
  let body = "";
114
165
  req.on("data", (chunk) => {
@@ -171,6 +222,13 @@ var WebChatServer = class {
171
222
  // src/web-chat/index.ts
172
223
  import { execSync } from "child_process";
173
224
  import chalk from "chalk";
225
+ var HEARTBEAT_INTERVAL_OPTIONS = [6e4, 3e5, 18e5, 36e5, 216e5];
226
+ function formatHeartbeatInterval(intervalMs) {
227
+ const minutes = intervalMs / 6e4;
228
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"}`;
229
+ const hours = minutes / 60;
230
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
231
+ }
174
232
  async function extractAndSaveLearnings(responseText) {
175
233
  const learnPattern = /<<LEARN:\s*(.+?)>>/g;
176
234
  const matches = [...responseText.matchAll(learnPattern)];
@@ -303,25 +361,45 @@ async function logChatInteraction(userMessage, agentResponse) {
303
361
  }
304
362
  async function runNarratedHeartbeat(server) {
305
363
  const { loadConfig } = await import("./config-FL4VJVKZ.js");
306
- const { flushQueue } = await import("./queue-D3MRKABU.js");
307
- const { runAutonomyCycle } = await import("./autonomy-6UWPXTPD.js");
364
+ const { flushQueue } = await import("./queue-2ZBKDFX3.js");
365
+ const { runAutonomyCycle } = await import("./autonomy-XUKCAZM3.js");
308
366
  const { buildReflectionPrompt } = await import("./prompt-builder-KJKFCGM7.js");
309
367
  const { generateResponse } = await import("./llm-OGOYCWBH.js");
310
368
  const { addLearning } = await import("./memory-OIAH33G2.js");
311
369
  const { loadStrategy, saveStrategy } = await import("./strategy-Z4JSFHSP.js");
312
- const config = loadConfig();
313
- const intervalMs = config.runtime?.heartbeatIntervalMs ?? 3e5;
314
- const maxActions = config.runtime?.actionsPerHeartbeat ?? 4;
315
370
  let heartbeatCount = 0;
316
- console.log(chalk.cyan(` [Agent] Autonomous heartbeat started (every ${Math.round(intervalMs / 6e4)} min)`));
371
+ let lastIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 3e5;
372
+ console.log(chalk.cyan(` [Agent] Autonomous heartbeat started (every ${Math.round(lastIntervalMs / 6e4)} min)`));
317
373
  while (true) {
374
+ const runtimeConfig = loadConfig();
375
+ const intervalMs = runtimeConfig.runtime?.heartbeatIntervalMs ?? 3e5;
376
+ const maxActions = runtimeConfig.runtime?.actionsPerHeartbeat ?? 4;
377
+ if (intervalMs !== lastIntervalMs) {
378
+ server.pushAgentEvent("narration", `Heartbeat interval updated to ${formatHeartbeatInterval(intervalMs)}.`);
379
+ console.log(chalk.cyan(` [Agent] Heartbeat interval updated to ${Math.round(intervalMs / 6e4)} min`));
380
+ lastIntervalMs = intervalMs;
381
+ }
318
382
  const jitter = Math.floor(Math.random() * intervalMs * 0.3);
319
383
  const sleepMs = heartbeatCount === 0 ? 1e4 : intervalMs + jitter;
320
384
  const wakeUpAt = Date.now() + sleepMs;
321
385
  if (heartbeatCount > 0) {
322
386
  server.pushAgentEvent("sleep", "sleeping", { wakeUpAt });
323
387
  }
324
- await new Promise((r) => setTimeout(r, sleepMs));
388
+ let sleptMs = 0;
389
+ let intervalChangedDuringSleep = false;
390
+ while (sleptMs < sleepMs) {
391
+ const chunkMs = Math.min(1e3, sleepMs - sleptMs);
392
+ await new Promise((r) => setTimeout(r, chunkMs));
393
+ sleptMs += chunkMs;
394
+ const liveIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 3e5;
395
+ if (liveIntervalMs !== intervalMs) {
396
+ intervalChangedDuringSleep = true;
397
+ break;
398
+ }
399
+ }
400
+ if (intervalChangedDuringSleep) {
401
+ continue;
402
+ }
325
403
  heartbeatCount++;
326
404
  console.log(chalk.cyan(` [Agent] Heartbeat #${heartbeatCount}`));
327
405
  server.pushAgentEvent("wake", `Waking up... heartbeat #${heartbeatCount}`);
@@ -420,6 +498,7 @@ async function runNarratedHeartbeat(server) {
420
498
  }
421
499
  async function startWebChat() {
422
500
  const identity = loadIdentity();
501
+ const { loadConfig: loadRuntimeConfig, saveConfig } = await import("./config-FL4VJVKZ.js");
423
502
  const server = new WebChatServer();
424
503
  server.setIdentity({
425
504
  name: identity.name,
@@ -427,6 +506,25 @@ async function startWebChat() {
427
506
  bio: identity.bio,
428
507
  profileImage: identity.profileImage
429
508
  });
509
+ server.setHeartbeatConfigHandlers({
510
+ getIntervalMs: () => {
511
+ const config = loadRuntimeConfig();
512
+ return config.runtime?.heartbeatIntervalMs ?? 3e5;
513
+ },
514
+ setIntervalMs: (intervalMs) => {
515
+ if (!HEARTBEAT_INTERVAL_OPTIONS.includes(intervalMs)) {
516
+ throw new Error("Unsupported heartbeat interval.");
517
+ }
518
+ const config = loadRuntimeConfig();
519
+ config.runtime = {
520
+ heartbeatIntervalMs: intervalMs,
521
+ actionsPerHeartbeat: config.runtime?.actionsPerHeartbeat ?? 4,
522
+ enabled: true
523
+ };
524
+ saveConfig(config);
525
+ server.pushAgentEvent("narration", `Heartbeat interval set to ${formatHeartbeatInterval(intervalMs)}.`);
526
+ }
527
+ });
430
528
  const chatHistory = [];
431
529
  let systemPrompt = null;
432
530
  let messageCount = 0;
@@ -516,4 +614,4 @@ export {
516
614
  openBrowser,
517
615
  startWebChat
518
616
  };
519
- //# sourceMappingURL=web-chat-O24HGJVE.js.map
617
+ //# sourceMappingURL=web-chat-ZZ65DUID.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 } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\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 AgentActivity {\n type: \"heartbeat\" | \"action\" | \"status\";\n message: string;\n timestamp: number;\n}\n\n/** Messages pushed from the heartbeat into the chat UI */\nexport interface AgentChatEvent {\n type: \"narration\" | \"tweet\" | \"action\" | \"sleep\" | \"wake\" | \"error\";\n content: string;\n timestamp: number;\n meta?: Record<string, unknown>;\n}\n\ninterface AgentIdentity {\n name: string;\n handle: string;\n bio?: string;\n profileImage?: string;\n}\n\ninterface HeartbeatConfigHandlers {\n getIntervalMs: () => number | Promise<number>;\n setIntervalMs: (intervalMs: number) => void | Promise<void>;\n}\n\nconst ALLOWED_HEARTBEAT_INTERVALS = [60_000, 300_000, 1_800_000, 3_600_000, 21_600_000] as const;\n\nexport class WebChatServer {\n private server: http.Server | null = null;\n private port: number;\n private messages: ChatMessage[] = [];\n private activities: AgentActivity[] = [];\n private agentEvents: AgentChatEvent[] = [];\n private onUserMessage?: (message: string) => Promise<string>;\n private identity?: AgentIdentity;\n private heartbeatConfigHandlers?: HeartbeatConfigHandlers;\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 setHeartbeatConfigHandlers(handlers: HeartbeatConfigHandlers) {\n this.heartbeatConfigHandlers = handlers;\n }\n\n /** Push an activity update (from heartbeat) to the UI — kept for backwards compat */\n pushActivity(type: AgentActivity[\"type\"], message: string) {\n this.activities.push({ type, message, timestamp: Date.now() });\n if (this.activities.length > 100) {\n this.activities = this.activities.slice(-100);\n }\n }\n\n /** Push an event into the chat stream (narration, tweet card, sleep state, etc.) */\n pushAgentEvent(type: AgentChatEvent[\"type\"], content: string, meta?: Record<string, unknown>) {\n this.agentEvents.push({ type, content, timestamp: Date.now(), meta });\n if (this.agentEvents.length > 200) {\n this.agentEvents = this.agentEvents.slice(-200);\n }\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 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 // 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\n if (url.pathname === \"/api/messages\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ messages: this.messages }));\n return;\n }\n\n // API: Get agent activity feed (legacy, kept for compat)\n if (url.pathname === \"/api/activity\" && req.method === \"GET\") {\n const since = parseInt(url.searchParams.get(\"since\") || \"0\", 10);\n const newActivities = this.activities.filter((a) => a.timestamp > since);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ activities: newActivities }));\n return;\n }\n\n // API: Get agent events (narration, tweets, sleep) — polled by chat UI\n if (url.pathname === \"/api/agent-events\" && req.method === \"GET\") {\n const since = parseInt(url.searchParams.get(\"since\") || \"0\", 10);\n const newEvents = this.agentEvents.filter((e) => e.timestamp > since);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ events: newEvents }));\n return;\n }\n\n // API: Read heartbeat interval config\n if (url.pathname === \"/api/heartbeat-config\" && req.method === \"GET\") {\n if (!this.heartbeatConfigHandlers) {\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Heartbeat config unavailable\" }));\n return;\n }\n\n try {\n const intervalMs = await this.heartbeatConfigHandlers.getIntervalMs();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ intervalMs, allowedIntervals: ALLOWED_HEARTBEAT_INTERVALS }));\n } catch {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to load heartbeat config\" }));\n }\n return;\n }\n\n // API: Update heartbeat interval config\n if (url.pathname === \"/api/heartbeat-config\" && req.method === \"POST\") {\n if (!this.heartbeatConfigHandlers) {\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Heartbeat config unavailable\" }));\n return;\n }\n const handlers = this.heartbeatConfigHandlers;\n\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const parsed = JSON.parse(body) as { intervalMs?: number };\n const intervalMs = parsed.intervalMs;\n\n if (\n typeof intervalMs !== \"number\" ||\n !Number.isInteger(intervalMs) ||\n !ALLOWED_HEARTBEAT_INTERVALS.includes(intervalMs as (typeof ALLOWED_HEARTBEAT_INTERVALS)[number])\n ) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unsupported heartbeat interval\" }));\n return;\n }\n\n await handlers.setIntervalMs(intervalMs);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ success: true, intervalMs }));\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 // 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 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 // 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 this.port++;\n this.server?.close();\n this.start().then(resolve).catch(reject);\n } else {\n reject(error);\n }\n });\n });\n }\n\n stop() {\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n }\n}\n","/**\n * Web chat integration with autonomous heartbeat\n */\n\nimport { WebChatServer } from \"./server.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\n\nconst HEARTBEAT_INTERVAL_OPTIONS = [60_000, 300_000, 1_800_000, 3_600_000, 21_600_000] as const;\n\nfunction formatHeartbeatInterval(intervalMs: number): string {\n const minutes = intervalMs / 60_000;\n if (minutes < 60) return `${minutes} minute${minutes === 1 ? \"\" : \"s\"}`;\n const hours = minutes / 60;\n return `${hours} hour${hours === 1 ? \"\" : \"s\"}`;\n}\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:{json}>> tags from response, apply updates to identity/strategy/goals, 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].trim());\n\n // Apply identity changes\n if (update.identity) {\n const { loadIdentity, mutateIdentity, saveIdentity } = await import(\"../identity/index.js\");\n let identity = loadIdentity();\n\n const identityFields: Record<string, string> = {\n tone: \"tone\",\n worldview: \"worldview\",\n conflictStyle: \"conflictStyle\",\n vocabularyStyle: \"vocabularyStyle\",\n tweetStyle: \"tweetStyle\",\n };\n\n // Simple string/enum fields\n for (const [key, field] of Object.entries(identityFields)) {\n if (update.identity[key] !== undefined) {\n identity = mutateIdentity(identity, field, update.identity[key], \"training-chat\");\n console.log(chalk.dim(` [Training] Updated ${field}`));\n }\n }\n\n // Array fields (replace entire array)\n const arrayFields = [\"coreValues\", \"topics\", \"avoidTopics\", \"goals\", \"boundaries\", \"catchphrases\", \"heroes\"];\n for (const field of arrayFields) {\n if (update.identity[field] !== undefined && Array.isArray(update.identity[field])) {\n identity = mutateIdentity(identity, field, update.identity[field], \"training-chat\");\n console.log(chalk.dim(` [Training] Updated ${field}`));\n }\n }\n\n // Traits (merge, don't replace)\n if (update.identity.traits && typeof update.identity.traits === \"object\") {\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] Updated traits.${trait} = ${value}`));\n }\n }\n }\n\n // Engagement strategy (merge)\n if (update.identity.engagementStrategy && typeof update.identity.engagementStrategy === \"object\") {\n for (const [key, value] of Object.entries(update.identity.engagementStrategy)) {\n identity = mutateIdentity(identity, `engagementStrategy.${key}`, value, \"training-chat\");\n console.log(chalk.dim(` [Training] Updated engagementStrategy.${key}`));\n }\n }\n\n saveIdentity(identity);\n }\n\n // Apply strategy changes\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.experiments) {\n strategy.experiments.push(...update.strategy.experiments);\n }\n if (update.strategy.peopleToEngage) {\n strategy.peopleToEngage.push(...update.strategy.peopleToEngage);\n }\n\n strategy.lastUpdated = new Date().toISOString();\n saveStrategy(strategy);\n console.log(chalk.dim(` [Training] Updated strategy`));\n }\n\n // Apply standalone learning\n if (update.learning) {\n const { addLearning } = await import(\"../memory/index.js\");\n addLearning(\n update.learning.content,\n \"training-chat\",\n update.learning.tags || [\"training\"],\n );\n console.log(chalk.dim(` [Training] Saved learning: ${update.learning.content}`));\n }\n\n // Apply reflection to evolution journal\n if (update.reflection) {\n const { loadIdentity, saveIdentity } = await import(\"../identity/index.js\");\n const identity = loadIdentity();\n identity.evolutionJournal.push({\n date: new Date().toISOString(),\n reflection: update.reflection,\n });\n saveIdentity(identity);\n console.log(chalk.dim(` [Training] Added reflection to evolution journal`));\n }\n\n // Apply goal updates\n if (update.goalUpdates && Array.isArray(update.goalUpdates)) {\n const { loadGoals, saveGoals } = await import(\"../memory/goals.js\");\n const tracker = loadGoals();\n\n for (const gu of update.goalUpdates) {\n const existing = tracker.goals.find((g: { goal: string }) => g.goal === gu.goal);\n if (existing) {\n existing.progress = gu.progress;\n existing.lastUpdated = new Date().toISOString();\n } else {\n tracker.goals.push({\n goal: gu.goal,\n progress: gu.progress,\n lastUpdated: new Date().toISOString(),\n });\n }\n }\n\n tracker.lastReviewed = new Date().toISOString();\n saveGoals(tracker);\n console.log(chalk.dim(` [Training] Updated ${update.goalUpdates.length} goal(s)`));\n }\n\n } catch (err) {\n console.error(chalk.dim(` [Training] Failed to parse training update: ${(err as Error).message}`));\n }\n }\n\n // Strip the training tags from the response the user sees\n return responseText.replace(/<<TRAINING:[\\s\\S]*?>>/g, \"\").trim();\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\n/**\n * Run the autonomous heartbeat in the background, narrating to the chat UI\n */\nasync function runNarratedHeartbeat(server: WebChatServer): Promise<void> {\n const { loadConfig } = await import(\"../utils/config.js\");\n const { flushQueue } = await import(\"../scheduler/queue.js\");\n const { runAutonomyCycle } = await import(\"../runtime/autonomy.js\");\n const { buildReflectionPrompt } = await import(\"../runtime/prompt-builder.js\");\n const { generateResponse } = await import(\"../runtime/llm.js\");\n const { addLearning } = await import(\"../memory/index.js\");\n const { loadStrategy, saveStrategy } = await import(\"../memory/strategy.js\");\n\n let heartbeatCount = 0;\n let lastIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 300_000;\n\n console.log(chalk.cyan(` [Agent] Autonomous heartbeat started (every ${Math.round(lastIntervalMs / 60_000)} min)`));\n\n while (true) {\n const runtimeConfig = loadConfig();\n const intervalMs = runtimeConfig.runtime?.heartbeatIntervalMs ?? 300_000;\n const maxActions = runtimeConfig.runtime?.actionsPerHeartbeat ?? 4;\n\n if (intervalMs !== lastIntervalMs) {\n server.pushAgentEvent(\"narration\", `Heartbeat interval updated to ${formatHeartbeatInterval(intervalMs)}.`);\n console.log(chalk.cyan(` [Agent] Heartbeat interval updated to ${Math.round(intervalMs / 60_000)} min`));\n lastIntervalMs = intervalMs;\n }\n\n // Sleep first, then heartbeat\n const jitter = Math.floor(Math.random() * intervalMs * 0.3);\n const sleepMs = heartbeatCount === 0 ? 10_000 : intervalMs + jitter;\n const wakeUpAt = Date.now() + sleepMs;\n\n // Show sleep state in chat (skip for first quick boot)\n if (heartbeatCount > 0) {\n server.pushAgentEvent(\"sleep\", \"sleeping\", { wakeUpAt });\n }\n\n // Sleep in short chunks so heartbeat interval changes apply immediately.\n let sleptMs = 0;\n let intervalChangedDuringSleep = false;\n while (sleptMs < sleepMs) {\n const chunkMs = Math.min(1000, sleepMs - sleptMs);\n await new Promise((r) => setTimeout(r, chunkMs));\n sleptMs += chunkMs;\n\n const liveIntervalMs = loadConfig().runtime?.heartbeatIntervalMs ?? 300_000;\n if (liveIntervalMs !== intervalMs) {\n intervalChangedDuringSleep = true;\n break;\n }\n }\n\n if (intervalChangedDuringSleep) {\n continue;\n }\n\n heartbeatCount++;\n console.log(chalk.cyan(` [Agent] Heartbeat #${heartbeatCount}`));\n server.pushAgentEvent(\"wake\", `Waking up... heartbeat #${heartbeatCount}`);\n\n try {\n // 1. Flush queue\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n server.pushAgentEvent(\"action\", `Flushed ${flushed.posted} queued post(s)`);\n }\n } catch {\n // Queue flush is best-effort\n }\n\n // 2. Run tool-driven autonomy cycle\n server.pushAgentEvent(\"narration\", \"Observing timeline and planning actions...\");\n const cycle = await runAutonomyCycle(maxActions);\n\n if (cycle.timeline.length > 0 || cycle.mentions.length > 0) {\n server.pushAgentEvent(\"narration\", `Found ${cycle.timeline.length} timeline posts and ${cycle.mentions.length} mentions`);\n }\n\n if (cycle.actions.length === 0) {\n server.pushAgentEvent(\"narration\", \"Nothing interesting right now, going back to sleep\");\n console.log(chalk.dim(` [Agent] No actions this heartbeat`));\n continue;\n }\n\n // 3. Report results — push tweet cards for posts, narration for everything else\n for (let i = 0; i < cycle.results.length; i++) {\n const result = cycle.results[i];\n const action = cycle.actions[i];\n if (!action) continue;\n\n if (result.success) {\n if (action.action === \"post\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, { status: \"success\" });\n } else if (action.action === \"reply\" && action.content) {\n server.pushAgentEvent(\"action\", `Replied to tweet: \"${action.content.slice(0, 80)}\"`);\n } else if (action.action === \"like\") {\n server.pushAgentEvent(\"action\", `Liked a tweet`);\n } else if (action.action === \"retweet\") {\n server.pushAgentEvent(\"action\", `Retweeted`);\n } else if (action.action === \"follow\") {\n server.pushAgentEvent(\"action\", `Followed @${action.handle}`);\n } else if (action.action === \"schedule\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, { status: \"Scheduled\" });\n } else {\n server.pushAgentEvent(\"action\", `${action.action}${result.detail ? `: ${result.detail}` : \"\"}`);\n }\n console.log(chalk.green(` [Agent] ✓ ${result.action}`));\n } else {\n if (action.action === \"post\" && action.content) {\n server.pushAgentEvent(\"tweet\", action.content, { status: `Failed: ${result.error}` });\n } else {\n server.pushAgentEvent(\"error\", `${result.action} failed: ${result.error}`);\n }\n console.log(chalk.red(` [Agent] ✗ ${result.action}: ${result.error}`));\n }\n }\n\n for (const feedback of cycle.policyFeedback) {\n server.pushAgentEvent(\"narration\", `Policy adjusted plan: ${feedback}`);\n }\n\n // Reflection phase — every 3rd heartbeat, review performance\n if (heartbeatCount % 3 === 0) {\n try {\n server.pushAgentEvent(\"narration\", \"Reflecting on recent performance...\");\n const reflectionPrompt = buildReflectionPrompt(cycle.results);\n const reflectionResponse = await generateResponse(\n `You are ${loadIdentity().name}. Reflect honestly on your performance.`,\n reflectionPrompt,\n );\n\n // Parse reflection JSON\n const jsonMatch = reflectionResponse.content.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const reflection = JSON.parse(jsonMatch[0]);\n if (reflection.learning && reflection.learning !== \"null\") {\n addLearning(reflection.learning, \"reflection\", [\"heartbeat\", \"performance\"]);\n server.pushAgentEvent(\"narration\", `Learned: ${reflection.learning}`);\n console.log(chalk.dim(` [Agent] Reflection learning: ${reflection.learning}`));\n }\n if (reflection.strategyUpdate && reflection.strategyUpdate !== \"null\") {\n const strategy = loadStrategy();\n strategy.experiments.push({\n description: reflection.strategyUpdate,\n status: \"pending\",\n });\n strategy.lastUpdated = new Date().toISOString();\n saveStrategy(strategy);\n server.pushAgentEvent(\"narration\", `Strategy: ${reflection.strategyUpdate}`);\n console.log(chalk.dim(` [Agent] Strategy update: ${reflection.strategyUpdate}`));\n }\n } catch {\n // Couldn't parse reflection JSON, that's fine\n }\n }\n } catch (err) {\n console.log(chalk.dim(` [Agent] Reflection failed: ${(err as Error).message}`));\n }\n }\n\n } catch (error) {\n const msg = (error as Error).message;\n console.error(chalk.red(` [Agent] Heartbeat #${heartbeatCount} error: ${msg}`));\n server.pushAgentEvent(\"error\", `Something went wrong: ${msg}`);\n }\n }\n}\n\nexport async function startWebChat() {\n const identity = loadIdentity();\n const { loadConfig: loadRuntimeConfig, saveConfig } = await import(\"../utils/config.js\");\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 });\n\n server.setHeartbeatConfigHandlers({\n getIntervalMs: () => {\n const config = loadRuntimeConfig();\n return config.runtime?.heartbeatIntervalMs ?? 300_000;\n },\n setIntervalMs: (intervalMs) => {\n if (!HEARTBEAT_INTERVAL_OPTIONS.includes(intervalMs as (typeof HEARTBEAT_INTERVAL_OPTIONS)[number])) {\n throw new Error(\"Unsupported heartbeat interval.\");\n }\n\n const config = loadRuntimeConfig();\n config.runtime = {\n heartbeatIntervalMs: intervalMs,\n actionsPerHeartbeat: config.runtime?.actionsPerHeartbeat ?? 4,\n enabled: true,\n };\n saveConfig(config);\n server.pushAgentEvent(\"narration\", `Heartbeat interval set to ${formatHeartbeatInterval(intervalMs)}.`);\n },\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 // Build training 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();\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 llm set --provider <provider>` 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 training updates and learnings from response\n const afterTraining = await extractAndApplyTraining(response.content);\n const cleanResponse = await extractAndSaveLearnings(afterTraining);\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 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 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 autonomous heartbeat in background\n try {\n const { hasLLMKey } = await import(\"../runtime/llm.js\");\n if (hasLLMKey()) {\n runNarratedHeartbeat(server).catch((err) => {\n console.error(chalk.red(`Heartbeat failed to start: ${err}`));\n });\n } else {\n console.log(chalk.yellow(\" [Agent] No LLM key — autonomous heartbeat disabled. Run `spora llm set --provider <provider>` to enable.\"));\n server.pushActivity(\"status\", \"No LLM API key configured. Autonomous mode disabled.\");\n }\n } 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 server.stop();\n process.exit(0);\n });\n\n // Return the server instance for external control\n return server;\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=500,700`, { 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=500,700`, { 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=500,700`, { 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=500,700`, { 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"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getXClient,
3
3
  resetXClient
4
- } from "./chunk-HGNMHGAF.js";
4
+ } from "./chunk-PN5A6MCV.js";
5
5
  import "./chunk-YMGJQRKG.js";
6
6
  import "./chunk-NO3NQN67.js";
7
7
  import "./chunk-3RYCUGXE.js";
@@ -9,4 +9,4 @@ export {
9
9
  getXClient,
10
10
  resetXClient
11
11
  };
12
- //# sourceMappingURL=x-client-ASXVQ6EV.js.map
12
+ //# sourceMappingURL=x-client-YG7UCCNI.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spora",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
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/utils/crypto.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes, createHash } from \"node:crypto\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { paths, ensureDirectories } from \"./paths.js\";\n\nconst ALGORITHM = \"aes-256-gcm\";\n\nfunction deriveKey(): Buffer {\n const machineId = `spora-${hostname()}-${process.env.USER ?? \"default\"}`;\n return createHash(\"sha256\").update(machineId).digest();\n}\n\nexport function encrypt(data: string): string {\n const key = deriveKey();\n const iv = randomBytes(16);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n let encrypted = cipher.update(data, \"utf-8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n const authTag = cipher.getAuthTag().toString(\"hex\");\n\n return JSON.stringify({\n iv: iv.toString(\"hex\"),\n encrypted,\n authTag,\n });\n}\n\nexport function decrypt(payload: string): string {\n const key = deriveKey();\n const { iv, encrypted, authTag } = JSON.parse(payload);\n\n const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, \"hex\"));\n decipher.setAuthTag(Buffer.from(authTag, \"hex\"));\n\n let decrypted = decipher.update(encrypted, \"hex\", \"utf-8\");\n decrypted += decipher.final(\"utf-8\");\n\n return decrypted;\n}\n\nexport interface XCredentials {\n method: \"api\";\n apiKey?: string;\n apiSecret?: string;\n accessToken?: string;\n accessTokenSecret?: string;\n bearerToken?: string;\n}\n\nexport function saveCredentials(credentials: XCredentials): void {\n ensureDirectories();\n const encrypted = encrypt(JSON.stringify(credentials));\n writeFileSync(paths.credentials, encrypted);\n}\n\nexport function loadCredentials(): XCredentials {\n if (!existsSync(paths.credentials)) {\n throw new Error(\"No credentials found. Run `spora init` first.\");\n }\n const payload = readFileSync(paths.credentials, \"utf-8\");\n return JSON.parse(decrypt(payload)) as XCredentials;\n}\n"],"mappings":";;;;;;AAAA,SAAS,gBAAgB,kBAAkB,aAAa,kBAAkB;AAC1E,SAAS,cAAc,eAAe,kBAAkB;AACxD,SAAS,gBAAgB;AAGzB,IAAM,YAAY;AAElB,SAAS,YAAoB;AAC3B,QAAM,YAAY,SAAS,SAAS,CAAC,IAAI,QAAQ,IAAI,QAAQ,SAAS;AACtE,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO;AACvD;AAEO,SAAS,QAAQ,MAAsB;AAC5C,QAAM,MAAM,UAAU;AACtB,QAAM,KAAK,YAAY,EAAE;AACzB,QAAM,SAAS,eAAe,WAAW,KAAK,EAAE;AAEhD,MAAI,YAAY,OAAO,OAAO,MAAM,SAAS,KAAK;AAClD,eAAa,OAAO,MAAM,KAAK;AAC/B,QAAM,UAAU,OAAO,WAAW,EAAE,SAAS,KAAK;AAElD,SAAO,KAAK,UAAU;AAAA,IACpB,IAAI,GAAG,SAAS,KAAK;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,QAAQ,SAAyB;AAC/C,QAAM,MAAM,UAAU;AACtB,QAAM,EAAE,IAAI,WAAW,QAAQ,IAAI,KAAK,MAAM,OAAO;AAErD,QAAM,WAAW,iBAAiB,WAAW,KAAK,OAAO,KAAK,IAAI,KAAK,CAAC;AACxE,WAAS,WAAW,OAAO,KAAK,SAAS,KAAK,CAAC;AAE/C,MAAI,YAAY,SAAS,OAAO,WAAW,OAAO,OAAO;AACzD,eAAa,SAAS,MAAM,OAAO;AAEnC,SAAO;AACT;AAWO,SAAS,gBAAgB,aAAiC;AAC/D,oBAAkB;AAClB,QAAM,YAAY,QAAQ,KAAK,UAAU,WAAW,CAAC;AACrD,gBAAc,MAAM,aAAa,SAAS;AAC5C;AAEO,SAAS,kBAAgC;AAC9C,MAAI,CAAC,WAAW,MAAM,WAAW,GAAG;AAClC,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,QAAM,UAAU,aAAa,MAAM,aAAa,OAAO;AACvD,SAAO,KAAK,MAAM,QAAQ,OAAO,CAAC;AACpC;","names":[]}