spora 0.7.7 → 0.7.9

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 (47) hide show
  1. package/dist/{autonomy-NNFTM5NW.js → autonomy-ZMFZRXDZ.js} +7 -7
  2. package/dist/{chunk-BBXHECZ5.js → chunk-6WBIVXOY.js} +1 -1
  3. package/dist/chunk-6WBIVXOY.js.map +1 -0
  4. package/dist/{chunk-JIMONWKO.js → chunk-73CWOI44.js} +4 -4
  5. package/dist/chunk-73CWOI44.js.map +1 -0
  6. package/dist/{chunk-ZLSDFYBR.js → chunk-AIGSCHZK.js} +2 -2
  7. package/dist/{chunk-CP6JWCLY.js → chunk-ER6TILYZ.js} +1 -25
  8. package/dist/{chunk-CP6JWCLY.js.map → chunk-ER6TILYZ.js.map} +1 -1
  9. package/dist/chunk-OLYPPXKP.js +69 -0
  10. package/dist/chunk-OLYPPXKP.js.map +1 -0
  11. package/dist/{chunk-5R4AJZHN.js → chunk-TKGB5LIN.js} +2 -2
  12. package/dist/chunk-TTDQZI5W.js +1699 -0
  13. package/dist/chunk-TTDQZI5W.js.map +1 -0
  14. package/dist/cli.js +28 -28
  15. package/dist/{client-AR5ZD6S4.js → client-3APKWQ6O.js} +3 -3
  16. package/dist/{colony-UGVYALOS.js → colony-7GZ2ODF2.js} +2 -2
  17. package/dist/{heartbeat-WJJSGUAQ.js → heartbeat-CUM7FIHS.js} +23 -7
  18. package/dist/heartbeat-CUM7FIHS.js.map +1 -0
  19. package/dist/heartbeat-narrative-B3RD3OPJ.js +11 -0
  20. package/dist/{init-6HY4ZPFJ.js → init-4SBU4H6F.js} +3 -3
  21. package/dist/mcp-server.js +20 -20
  22. package/dist/{memory-DTSLVSQG.js → memory-G4DNIGLT.js} +2 -2
  23. package/dist/{prompt-builder-ZFUZNQY2.js → prompt-builder-S6PJVEC5.js} +4 -4
  24. package/dist/{queue-QCGNDHH2.js → queue-YPBUUP22.js} +2 -2
  25. package/dist/web-chat/chat.html +64 -72
  26. package/dist/web-chat/logo2.png +0 -0
  27. package/dist/{web-chat-AKUEBSWS.js → web-chat-YRQQB435.js} +70 -31
  28. package/dist/web-chat-YRQQB435.js.map +1 -0
  29. package/dist/{x-client-S2LUVEKV.js → x-client-2HFEHHVE.js} +2 -2
  30. package/dist/x-client-2HFEHHVE.js.map +1 -0
  31. package/package.json +1 -1
  32. package/dist/chunk-BBXHECZ5.js.map +0 -1
  33. package/dist/chunk-JIMONWKO.js.map +0 -1
  34. package/dist/chunk-TTM54LQR.js +0 -2769
  35. package/dist/chunk-TTM54LQR.js.map +0 -1
  36. package/dist/heartbeat-WJJSGUAQ.js.map +0 -1
  37. package/dist/web-chat-AKUEBSWS.js.map +0 -1
  38. /package/dist/{autonomy-NNFTM5NW.js.map → autonomy-ZMFZRXDZ.js.map} +0 -0
  39. /package/dist/{chunk-ZLSDFYBR.js.map → chunk-AIGSCHZK.js.map} +0 -0
  40. /package/dist/{chunk-5R4AJZHN.js.map → chunk-TKGB5LIN.js.map} +0 -0
  41. /package/dist/{client-AR5ZD6S4.js.map → client-3APKWQ6O.js.map} +0 -0
  42. /package/dist/{colony-UGVYALOS.js.map → colony-7GZ2ODF2.js.map} +0 -0
  43. /package/dist/{memory-DTSLVSQG.js.map → heartbeat-narrative-B3RD3OPJ.js.map} +0 -0
  44. /package/dist/{init-6HY4ZPFJ.js.map → init-4SBU4H6F.js.map} +0 -0
  45. /package/dist/{prompt-builder-ZFUZNQY2.js.map → memory-G4DNIGLT.js.map} +0 -0
  46. /package/dist/{queue-QCGNDHH2.js.map → prompt-builder-S6PJVEC5.js.map} +0 -0
  47. /package/dist/{x-client-S2LUVEKV.js.map → queue-YPBUUP22.js.map} +0 -0
@@ -1,20 +1,24 @@
1
1
  import {
2
2
  runAutonomyCycle
3
- } from "./chunk-TTM54LQR.js";
4
- import "./chunk-5R4AJZHN.js";
3
+ } from "./chunk-TTDQZI5W.js";
4
+ import "./chunk-TKGB5LIN.js";
5
5
  import {
6
6
  flushQueue
7
- } from "./chunk-ZLSDFYBR.js";
7
+ } from "./chunk-AIGSCHZK.js";
8
8
  import {
9
9
  buildReflectionPrompt
10
- } from "./chunk-JIMONWKO.js";
10
+ } from "./chunk-73CWOI44.js";
11
11
  import {
12
12
  applyStrategyUpdate,
13
13
  loadStrategy,
14
14
  saveStrategy
15
15
  } from "./chunk-OTZNHIXT.js";
16
16
  import "./chunk-CAWWG3MD.js";
17
- import "./chunk-CP6JWCLY.js";
17
+ import "./chunk-ER6TILYZ.js";
18
+ import {
19
+ buildHeartbeatNarrative,
20
+ saveHeartbeatNarrative
21
+ } from "./chunk-OLYPPXKP.js";
18
22
  import {
19
23
  loadIdentity
20
24
  } from "./chunk-IULO3GRE.js";
@@ -30,7 +34,7 @@ import {
30
34
  } from "./chunk-QYFNAGNI.js";
31
35
  import {
32
36
  addLearning
33
- } from "./chunk-BBXHECZ5.js";
37
+ } from "./chunk-6WBIVXOY.js";
34
38
  import {
35
39
  ensureDirectories,
36
40
  paths
@@ -188,6 +192,18 @@ async function runHeartbeat(maxActions, heartbeatCount) {
188
192
  logger.warn(`Queue flush failed: ${error.message}`);
189
193
  }
190
194
  const cycle = await runAutonomyCycle(maxActions, heartbeatCount);
195
+ const narrative = buildHeartbeatNarrative({
196
+ heartbeatCount,
197
+ timelineCount: cycle.timeline.length,
198
+ mentionsCount: cycle.mentions.length,
199
+ actions: cycle.actions,
200
+ results: cycle.results
201
+ });
202
+ saveHeartbeatNarrative(
203
+ narrative.summary,
204
+ cycle.results.length === 0 ? true : cycle.results.every((r) => r.success)
205
+ );
206
+ logger.info(`Narrative: ${narrative.summary}`);
191
207
  logger.info(`Observed ${cycle.timeline.length} timeline posts and ${cycle.mentions.length} mentions.`);
192
208
  if (cycle.actions.length === 0) {
193
209
  logger.info("Planner returned no approved actions.");
@@ -250,4 +266,4 @@ export {
250
266
  requestStop,
251
267
  startHeartbeatLoop
252
268
  };
253
- //# sourceMappingURL=heartbeat-WJJSGUAQ.js.map
269
+ //# sourceMappingURL=heartbeat-CUM7FIHS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/heartbeat.ts","../src/runtime/telemetry.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { logger } from \"../utils/logger.js\";\nimport { loadConfig } from \"../utils/config.js\";\nimport { paths } from \"../utils/paths.js\";\nimport { flushQueue } from \"../scheduler/queue.js\";\nimport { runAutonomyCycle } from \"./autonomy.js\";\nimport { writeHeartbeatMetrics } from \"./telemetry.js\";\nimport { buildReflectionPrompt } from \"./prompt-builder.js\";\nimport { generateResponse } from \"./llm.js\";\nimport { addLearning } from \"../memory/index.js\";\nimport { applyStrategyUpdate, loadStrategy, saveStrategy } from \"../memory/strategy.js\";\nimport { loadIdentity } from \"../identity/index.js\";\nimport { buildHeartbeatNarrative, saveHeartbeatNarrative } from \"./heartbeat-narrative.js\";\n\nlet running = false;\n\nexport function isRunning(): boolean {\n return running;\n}\n\nexport function requestStop(): void {\n writeFileSync(paths.stopSignal, \"stop\");\n logger.info(\"Stop signal sent.\");\n}\n\nfunction shouldStop(): boolean {\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n return true;\n }\n return false;\n}\n\nfunction writePid(): void {\n writeFileSync(paths.runtimePid, String(process.pid));\n}\n\nfunction clearPid(): void {\n if (existsSync(paths.runtimePid)) {\n unlinkSync(paths.runtimePid);\n }\n}\n\nexport function getRunningPid(): number | null {\n if (!existsSync(paths.runtimePid)) return null;\n const pid = parseInt(readFileSync(paths.runtimePid, \"utf-8\").trim(), 10);\n if (isNaN(pid)) return null;\n\n // Check if process is actually running\n try {\n process.kill(pid, 0);\n return pid;\n } catch {\n // Process not running, clean up stale PID\n clearPid();\n return null;\n }\n}\n\nexport async function startHeartbeatLoop(): Promise<void> {\n // Check if already running\n const existingPid = getRunningPid();\n if (existingPid) {\n throw new Error(`Spora is already running (PID ${existingPid}). Run \\`spora stop\\` first.`);\n }\n\n running = true;\n writePid();\n\n const config = loadConfig();\n const intervalMs = config.runtime?.heartbeatIntervalMs ?? 300_000;\n const maxActions = config.runtime?.actionsPerHeartbeat ?? 3;\n\n logger.info(`Spora agent starting. Heartbeat interval: ${intervalMs / 1000}s, max actions: ${maxActions}`);\n console.log(`\\nSpora agent is running (PID ${process.pid})`);\n console.log(`Heartbeat every ${Math.round(intervalMs / 60_000)} minutes`);\n console.log(`Press Ctrl+C or run \\`spora stop\\` to stop.\\n`);\n\n // Handle graceful shutdown\n const shutdown = () => {\n logger.info(\"Shutting down...\");\n running = false;\n clearPid();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Clean any stale stop signal\n if (existsSync(paths.stopSignal)) {\n unlinkSync(paths.stopSignal);\n }\n\n let heartbeatCount = 0;\n\n while (running) {\n heartbeatCount++;\n logger.info(`=== Heartbeat #${heartbeatCount} ===`);\n\n try {\n await runHeartbeat(maxActions, heartbeatCount);\n } catch (error) {\n logger.error(\"Heartbeat error\", error);\n console.error(`Heartbeat #${heartbeatCount} failed: ${(error as Error).message}`);\n }\n\n // Check for stop signal\n if (shouldStop()) {\n logger.info(\"Stop signal received.\");\n break;\n }\n\n // Sleep with jitter\n const jitter = Math.floor(Math.random() * intervalMs * 0.3);\n const sleepMs = intervalMs + jitter;\n logger.info(`Sleeping ${Math.round(sleepMs / 1000)}s until next heartbeat...`);\n\n // Sleep in chunks so we can check for stop signals\n const chunkMs = 10_000;\n let slept = 0;\n while (slept < sleepMs && running) {\n await new Promise((r) => setTimeout(r, Math.min(chunkMs, sleepMs - slept)));\n slept += chunkMs;\n if (shouldStop()) {\n running = false;\n break;\n }\n }\n }\n\n clearPid();\n logger.info(\"Spora agent stopped.\");\n console.log(\"\\nSpora agent stopped.\");\n}\n\nasync function runHeartbeat(maxActions: number, heartbeatCount: number): Promise<void> {\n // 1. Flush any queued posts\n logger.info(\"Checking queue...\");\n try {\n const flushed = await flushQueue();\n if (flushed.posted > 0) {\n logger.info(`Flushed ${flushed.posted} queued posts.`);\n }\n } catch (error) {\n logger.warn(`Queue flush failed: ${(error as Error).message}`);\n }\n\n // 2. Run planner/executor cycle.\n const cycle = await runAutonomyCycle(maxActions, heartbeatCount);\n const narrative = buildHeartbeatNarrative({\n heartbeatCount,\n timelineCount: cycle.timeline.length,\n mentionsCount: cycle.mentions.length,\n actions: cycle.actions,\n results: cycle.results,\n });\n saveHeartbeatNarrative(\n narrative.summary,\n cycle.results.length === 0 ? true : cycle.results.every((r) => r.success),\n );\n logger.info(`Narrative: ${narrative.summary}`);\n logger.info(`Observed ${cycle.timeline.length} timeline posts and ${cycle.mentions.length} mentions.`);\n\n if (cycle.actions.length === 0) {\n logger.info(\"Planner returned no approved actions.\");\n return;\n }\n\n logger.info(`Executed ${cycle.results.length} action(s).`);\n\n // 3. Log results\n for (const result of cycle.results) {\n if (result.success) {\n logger.info(` [OK] ${result.action}${result.detail ? `: ${result.detail}` : \"\"}`);\n } else {\n logger.warn(` [FAIL] ${result.action}: ${result.error}`);\n }\n }\n\n for (const note of cycle.policyFeedback) {\n logger.info(` [POLICY] ${note}`);\n }\n\n const metrics = writeHeartbeatMetrics({\n timelineCount: cycle.timeline.length,\n mentionsCount: cycle.mentions.length,\n actions: cycle.actions,\n results: cycle.results,\n policyFeedbackCount: cycle.policyFeedback.length,\n });\n logger.info(\n `Metrics: interactionRatio=${metrics.interactionRatio.toFixed(2)}, repeatedFormatRate=${metrics.repeatedFormatRate.toFixed(2)}`\n );\n\n logger.info(`Heartbeat complete. ${cycle.results.filter((r) => r.success).length}/${cycle.results.length} actions succeeded.`);\n\n // Reflection phase — every 3rd heartbeat\n if (heartbeatCount % 3 === 0) {\n try {\n logger.info(\"Running reflection phase...\");\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 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 logger.info(`Reflection learning: ${reflection.learning}`);\n }\n if (reflection.strategyUpdate && reflection.strategyUpdate !== \"null\") {\n const strategy = loadStrategy();\n saveStrategy(applyStrategyUpdate(strategy, reflection.strategyUpdate));\n logger.info(`Strategy update: ${reflection.strategyUpdate}`);\n }\n } catch {\n // Couldn't parse reflection JSON\n }\n }\n } catch (err) {\n logger.warn(`Reflection failed: ${(err as Error).message}`);\n }\n }\n}\n","import { appendFileSync } from \"node:fs\";\nimport { paths, ensureDirectories } from \"../utils/paths.js\";\nimport type { AgentAction, ActionResult } from \"./decision-engine.js\";\n\nexport interface HeartbeatMetrics {\n timestamp: string;\n timelineCount: number;\n mentionsCount: number;\n actionCount: number;\n successCount: number;\n postCount: number;\n interactionCount: number;\n interactionRatio: number;\n repeatedFormatRate: number;\n policyRejectionCount: number;\n}\n\nfunction normalize(text: string): string {\n return text.toLowerCase().replace(/[^a-z0-9\\s]/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nfunction prefix(text: string, n = 6): string {\n return normalize(text).split(\" \").filter(Boolean).slice(0, n).join(\" \");\n}\n\nfunction repeatedFormatRate(actions: AgentAction[]): number {\n const posts = actions\n .filter((action) => action.action === \"post\" && action.content)\n .map((action) => action.content ?? \"\");\n\n if (posts.length <= 1) return 0;\n\n const seen = new Map<string, number>();\n for (const content of posts) {\n const key = prefix(content);\n seen.set(key, (seen.get(key) ?? 0) + 1);\n }\n\n let repeated = 0;\n for (const count of seen.values()) {\n if (count > 1) repeated += count;\n }\n\n return repeated / posts.length;\n}\n\nexport function writeHeartbeatMetrics(input: {\n timelineCount: number;\n mentionsCount: number;\n actions: AgentAction[];\n results: ActionResult[];\n policyFeedbackCount: number;\n}): HeartbeatMetrics {\n const interactionCount = input.actions.filter((a) => [\"reply\", \"like\", \"retweet\", \"follow\"].includes(a.action)).length;\n const postCount = input.actions.filter((a) => a.action === \"post\").length;\n const successCount = input.results.filter((r) => r.success).length;\n\n const metrics: HeartbeatMetrics = {\n timestamp: new Date().toISOString(),\n timelineCount: input.timelineCount,\n mentionsCount: input.mentionsCount,\n actionCount: input.actions.length,\n successCount,\n postCount,\n interactionCount,\n interactionRatio: input.actions.length > 0 ? interactionCount / input.actions.length : 0,\n repeatedFormatRate: repeatedFormatRate(input.actions),\n policyRejectionCount: input.policyFeedbackCount,\n };\n\n ensureDirectories();\n appendFileSync(paths.runtimeMetrics, JSON.stringify(metrics) + \"\\n\");\n return metrics;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,YAAY,eAAe,oBAAoB;;;ACApE,SAAS,sBAAsB;AAiB/B,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,YAAY,EAAE,QAAQ,gBAAgB,GAAG,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACnF;AAEA,SAAS,OAAO,MAAc,IAAI,GAAW;AAC3C,SAAO,UAAU,IAAI,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG;AACxE;AAEA,SAAS,mBAAmB,SAAgC;AAC1D,QAAM,QAAQ,QACX,OAAO,CAAC,WAAW,OAAO,WAAW,UAAU,OAAO,OAAO,EAC7D,IAAI,CAAC,WAAW,OAAO,WAAW,EAAE;AAEvC,MAAI,MAAM,UAAU,EAAG,QAAO;AAE9B,QAAM,OAAO,oBAAI,IAAoB;AACrC,aAAW,WAAW,OAAO;AAC3B,UAAM,MAAM,OAAO,OAAO;AAC1B,SAAK,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,EACxC;AAEA,MAAI,WAAW;AACf,aAAW,SAAS,KAAK,OAAO,GAAG;AACjC,QAAI,QAAQ,EAAG,aAAY;AAAA,EAC7B;AAEA,SAAO,WAAW,MAAM;AAC1B;AAEO,SAAS,sBAAsB,OAMjB;AACnB,QAAM,mBAAmB,MAAM,QAAQ,OAAO,CAAC,MAAM,CAAC,SAAS,QAAQ,WAAW,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE;AAChH,QAAM,YAAY,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AACnE,QAAM,eAAe,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAE5D,QAAM,UAA4B;AAAA,IAChC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,eAAe,MAAM;AAAA,IACrB,eAAe,MAAM;AAAA,IACrB,aAAa,MAAM,QAAQ;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,MAAM,QAAQ,SAAS,IAAI,mBAAmB,MAAM,QAAQ,SAAS;AAAA,IACvF,oBAAoB,mBAAmB,MAAM,OAAO;AAAA,IACpD,sBAAsB,MAAM;AAAA,EAC9B;AAEA,oBAAkB;AAClB,iBAAe,MAAM,gBAAgB,KAAK,UAAU,OAAO,IAAI,IAAI;AACnE,SAAO;AACT;;;AD3DA,IAAI,UAAU;AAEP,SAAS,YAAqB;AACnC,SAAO;AACT;AAEO,SAAS,cAAoB;AAClC,gBAAc,MAAM,YAAY,MAAM;AACtC,SAAO,KAAK,mBAAmB;AACjC;AAEA,SAAS,aAAsB;AAC7B,MAAI,WAAW,MAAM,UAAU,GAAG;AAChC,eAAW,MAAM,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,WAAiB;AACxB,gBAAc,MAAM,YAAY,OAAO,QAAQ,GAAG,CAAC;AACrD;AAEA,SAAS,WAAiB;AACxB,MAAI,WAAW,MAAM,UAAU,GAAG;AAChC,eAAW,MAAM,UAAU;AAAA,EAC7B;AACF;AAEO,SAAS,gBAA+B;AAC7C,MAAI,CAAC,WAAW,MAAM,UAAU,EAAG,QAAO;AAC1C,QAAM,MAAM,SAAS,aAAa,MAAM,YAAY,OAAO,EAAE,KAAK,GAAG,EAAE;AACvE,MAAI,MAAM,GAAG,EAAG,QAAO;AAGvB,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AAEN,aAAS;AACT,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,qBAAoC;AAExD,QAAM,cAAc,cAAc;AAClC,MAAI,aAAa;AACf,UAAM,IAAI,MAAM,iCAAiC,WAAW,8BAA8B;AAAA,EAC5F;AAEA,YAAU;AACV,WAAS;AAET,QAAM,SAAS,WAAW;AAC1B,QAAM,aAAa,OAAO,SAAS,uBAAuB;AAC1D,QAAM,aAAa,OAAO,SAAS,uBAAuB;AAE1D,SAAO,KAAK,6CAA6C,aAAa,GAAI,mBAAmB,UAAU,EAAE;AACzG,UAAQ,IAAI;AAAA,8BAAiC,QAAQ,GAAG,GAAG;AAC3D,UAAQ,IAAI,mBAAmB,KAAK,MAAM,aAAa,GAAM,CAAC,UAAU;AACxE,UAAQ,IAAI;AAAA,CAA+C;AAG3D,QAAM,WAAW,MAAM;AACrB,WAAO,KAAK,kBAAkB;AAC9B,cAAU;AACV,aAAS;AACT,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAG9B,MAAI,WAAW,MAAM,UAAU,GAAG;AAChC,eAAW,MAAM,UAAU;AAAA,EAC7B;AAEA,MAAI,iBAAiB;AAErB,SAAO,SAAS;AACd;AACA,WAAO,KAAK,kBAAkB,cAAc,MAAM;AAElD,QAAI;AACF,YAAM,aAAa,YAAY,cAAc;AAAA,IAC/C,SAAS,OAAO;AACd,aAAO,MAAM,mBAAmB,KAAK;AACrC,cAAQ,MAAM,cAAc,cAAc,YAAa,MAAgB,OAAO,EAAE;AAAA,IAClF;AAGA,QAAI,WAAW,GAAG;AAChB,aAAO,KAAK,uBAAuB;AACnC;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,aAAa,GAAG;AAC1D,UAAM,UAAU,aAAa;AAC7B,WAAO,KAAK,YAAY,KAAK,MAAM,UAAU,GAAI,CAAC,2BAA2B;AAG7E,UAAM,UAAU;AAChB,QAAI,QAAQ;AACZ,WAAO,QAAQ,WAAW,SAAS;AACjC,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,IAAI,SAAS,UAAU,KAAK,CAAC,CAAC;AAC1E,eAAS;AACT,UAAI,WAAW,GAAG;AAChB,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS;AACT,SAAO,KAAK,sBAAsB;AAClC,UAAQ,IAAI,wBAAwB;AACtC;AAEA,eAAe,aAAa,YAAoB,gBAAuC;AAErF,SAAO,KAAK,mBAAmB;AAC/B,MAAI;AACF,UAAM,UAAU,MAAM,WAAW;AACjC,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,KAAK,WAAW,QAAQ,MAAM,gBAAgB;AAAA,IACvD;AAAA,EACF,SAAS,OAAO;AACd,WAAO,KAAK,uBAAwB,MAAgB,OAAO,EAAE;AAAA,EAC/D;AAGA,QAAM,QAAQ,MAAM,iBAAiB,YAAY,cAAc;AAC/D,QAAM,YAAY,wBAAwB;AAAA,IACxC;AAAA,IACA,eAAe,MAAM,SAAS;AAAA,IAC9B,eAAe,MAAM,SAAS;AAAA,IAC9B,SAAS,MAAM;AAAA,IACf,SAAS,MAAM;AAAA,EACjB,CAAC;AACD;AAAA,IACE,UAAU;AAAA,IACV,MAAM,QAAQ,WAAW,IAAI,OAAO,MAAM,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO;AAAA,EAC1E;AACA,SAAO,KAAK,cAAc,UAAU,OAAO,EAAE;AAC7C,SAAO,KAAK,YAAY,MAAM,SAAS,MAAM,uBAAuB,MAAM,SAAS,MAAM,YAAY;AAErG,MAAI,MAAM,QAAQ,WAAW,GAAG;AAC9B,WAAO,KAAK,uCAAuC;AACnD;AAAA,EACF;AAEA,SAAO,KAAK,YAAY,MAAM,QAAQ,MAAM,aAAa;AAGzD,aAAW,UAAU,MAAM,SAAS;AAClC,QAAI,OAAO,SAAS;AAClB,aAAO,KAAK,UAAU,OAAO,MAAM,GAAG,OAAO,SAAS,KAAK,OAAO,MAAM,KAAK,EAAE,EAAE;AAAA,IACnF,OAAO;AACL,aAAO,KAAK,YAAY,OAAO,MAAM,KAAK,OAAO,KAAK,EAAE;AAAA,IAC1D;AAAA,EACF;AAEA,aAAW,QAAQ,MAAM,gBAAgB;AACvC,WAAO,KAAK,cAAc,IAAI,EAAE;AAAA,EAClC;AAEA,QAAM,UAAU,sBAAsB;AAAA,IACpC,eAAe,MAAM,SAAS;AAAA,IAC9B,eAAe,MAAM,SAAS;AAAA,IAC9B,SAAS,MAAM;AAAA,IACf,SAAS,MAAM;AAAA,IACf,qBAAqB,MAAM,eAAe;AAAA,EAC5C,CAAC;AACD,SAAO;AAAA,IACL,6BAA6B,QAAQ,iBAAiB,QAAQ,CAAC,CAAC,wBAAwB,QAAQ,mBAAmB,QAAQ,CAAC,CAAC;AAAA,EAC/H;AAEA,SAAO,KAAK,uBAAuB,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,MAAM,QAAQ,MAAM,qBAAqB;AAG7H,MAAI,iBAAiB,MAAM,GAAG;AAC5B,QAAI;AACF,aAAO,KAAK,6BAA6B;AACzC,YAAM,mBAAmB,sBAAsB,MAAM,OAAO;AAC5D,YAAM,qBAAqB,MAAM;AAAA,QAC/B,WAAW,aAAa,EAAE,IAAI;AAAA,QAC9B;AAAA,MACF;AAEA,YAAM,YAAY,mBAAmB,QAAQ,MAAM,aAAa;AAChE,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,aAAa,KAAK,MAAM,UAAU,CAAC,CAAC;AAC1C,cAAI,WAAW,YAAY,WAAW,aAAa,QAAQ;AACzD,wBAAY,WAAW,UAAU,cAAc,CAAC,aAAa,aAAa,CAAC;AAC3E,mBAAO,KAAK,wBAAwB,WAAW,QAAQ,EAAE;AAAA,UAC3D;AACA,cAAI,WAAW,kBAAkB,WAAW,mBAAmB,QAAQ;AACrE,kBAAM,WAAW,aAAa;AAC9B,yBAAa,oBAAoB,UAAU,WAAW,cAAc,CAAC;AACrE,mBAAO,KAAK,oBAAoB,WAAW,cAAc,EAAE;AAAA,UAC7D;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,KAAK,sBAAuB,IAAc,OAAO,EAAE;AAAA,IAC5D;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,11 @@
1
+ import {
2
+ buildHeartbeatNarrative,
3
+ saveHeartbeatNarrative
4
+ } from "./chunk-OLYPPXKP.js";
5
+ import "./chunk-6WBIVXOY.js";
6
+ import "./chunk-ZWKTKWS6.js";
7
+ export {
8
+ buildHeartbeatNarrative,
9
+ saveHeartbeatNarrative
10
+ };
11
+ //# sourceMappingURL=heartbeat-narrative-B3RD3OPJ.js.map
@@ -276,7 +276,7 @@ async function loginFlow() {
276
276
  console.log(chalk.green("\u2713 Logged in!\n"));
277
277
  console.log(chalk.gray("Opening chat interface...\n"));
278
278
  try {
279
- const { startWebChat } = await import("./web-chat-AKUEBSWS.js");
279
+ const { startWebChat } = await import("./web-chat-YRQQB435.js");
280
280
  await startWebChat();
281
281
  } catch (error) {
282
282
  console.log(chalk.yellow(`Could not start chat interface: ${error.message}
@@ -368,7 +368,7 @@ async function showDoneAndOpenChat() {
368
368
  console.log(chalk.bold.cyan("\u2501\u2501\u2501 Your Spore is Ready! \u2501\u2501\u2501\n"));
369
369
  console.log(chalk.gray("Opening chat interface...\n"));
370
370
  try {
371
- const { startWebChat } = await import("./web-chat-AKUEBSWS.js");
371
+ const { startWebChat } = await import("./web-chat-YRQQB435.js");
372
372
  await startWebChat();
373
373
  } catch (error) {
374
374
  console.log(chalk.yellow(`Could not start chat interface: ${error.message}
@@ -506,4 +506,4 @@ async function runInit(token) {
506
506
  export {
507
507
  runInit
508
508
  };
509
- //# sourceMappingURL=init-6HY4ZPFJ.js.map
509
+ //# sourceMappingURL=init-4SBU4H6F.js.map
@@ -21,7 +21,7 @@ import {
21
21
  loadLearnings,
22
22
  loadRelationships,
23
23
  updateRelationship
24
- } from "./chunk-BBXHECZ5.js";
24
+ } from "./chunk-6WBIVXOY.js";
25
25
  import "./chunk-ZWKTKWS6.js";
26
26
 
27
27
  // src/mcp-server.ts
@@ -388,7 +388,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
388
388
  },
389
389
  async ({ content }) => {
390
390
  try {
391
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
391
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
392
392
  const client = await getXClient();
393
393
  const result = await client.postTweet(content);
394
394
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -406,7 +406,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
406
406
  },
407
407
  async ({ tweetId, content }) => {
408
408
  try {
409
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
409
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
410
410
  const client = await getXClient();
411
411
  const result = await client.replyToTweet(tweetId, content);
412
412
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -421,7 +421,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
421
421
  { tweetId: z.string().describe("The ID of the tweet to like") },
422
422
  async ({ tweetId }) => {
423
423
  try {
424
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
424
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
425
425
  const client = await getXClient();
426
426
  const result = await client.likeTweet(tweetId);
427
427
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -436,7 +436,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
436
436
  { tweetId: z.string().describe("The ID of the tweet to retweet") },
437
437
  async ({ tweetId }) => {
438
438
  try {
439
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
439
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
440
440
  const client = await getXClient();
441
441
  const result = await client.retweet(tweetId);
442
442
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -451,7 +451,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
451
451
  { userId: z.string().describe("The user ID or handle to follow") },
452
452
  async ({ userId }) => {
453
453
  try {
454
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
454
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
455
455
  const client = await getXClient();
456
456
  const result = await client.followUser(userId);
457
457
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -466,7 +466,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
466
466
  { userId: z.string().describe("The user ID or handle to unfollow") },
467
467
  async ({ userId }) => {
468
468
  try {
469
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
469
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
470
470
  const client = await getXClient();
471
471
  const result = await client.unfollowUser(userId);
472
472
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -481,7 +481,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
481
481
  { count: z.number().optional().describe("Number of tweets to fetch (default 20)") },
482
482
  async ({ count }) => {
483
483
  try {
484
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
484
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
485
485
  const client = await getXClient();
486
486
  const result = await client.getTimeline({ count: count ?? 20 });
487
487
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -496,7 +496,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
496
496
  { count: z.number().optional().describe("Number of mentions to fetch (default 20)") },
497
497
  async ({ count }) => {
498
498
  try {
499
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
499
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
500
500
  const client = await getXClient();
501
501
  const result = await client.getMentions({ count: count ?? 20 });
502
502
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -514,7 +514,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
514
514
  },
515
515
  async ({ query, count }) => {
516
516
  try {
517
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
517
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
518
518
  const client = await getXClient();
519
519
  const result = await client.searchTweets(query, { count: count ?? 20 });
520
520
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -529,7 +529,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
529
529
  { handle: z.string().describe("X handle (without @)") },
530
530
  async ({ handle }) => {
531
531
  try {
532
- const { getXClient } = await import("./x-client-S2LUVEKV.js");
532
+ const { getXClient } = await import("./x-client-2HFEHHVE.js");
533
533
  const client = await getXClient();
534
534
  const result = await client.getProfile(handle);
535
535
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -547,7 +547,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
547
547
  },
548
548
  async ({ content, scheduledFor }) => {
549
549
  try {
550
- const { addToQueue } = await import("./queue-QCGNDHH2.js");
550
+ const { addToQueue } = await import("./queue-YPBUUP22.js");
551
551
  const entry = addToQueue(content, scheduledFor);
552
552
  return {
553
553
  content: [
@@ -565,7 +565,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
565
565
  {},
566
566
  async () => {
567
567
  try {
568
- const { flushQueue } = await import("./queue-QCGNDHH2.js");
568
+ const { flushQueue } = await import("./queue-YPBUUP22.js");
569
569
  const results = await flushQueue();
570
570
  return {
571
571
  content: [
@@ -586,7 +586,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
586
586
  { message: z.string().optional().describe("Optional message to post to the Colony community") },
587
587
  async ({ message }) => {
588
588
  try {
589
- const { colonyCheckin } = await import("./colony-UGVYALOS.js");
589
+ const { colonyCheckin } = await import("./colony-7GZ2ODF2.js");
590
590
  const result = await colonyCheckin(message);
591
591
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
592
592
  } catch (error) {
@@ -600,7 +600,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
600
600
  {},
601
601
  async () => {
602
602
  try {
603
- const { getColonyMemory } = await import("./colony-UGVYALOS.js");
603
+ const { getColonyMemory } = await import("./colony-7GZ2ODF2.js");
604
604
  const memory = getColonyMemory();
605
605
  return { content: [{ type: "text", text: JSON.stringify(memory, null, 2) }] };
606
606
  } catch (error) {
@@ -614,7 +614,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
614
614
  {},
615
615
  async () => {
616
616
  try {
617
- const { getActivePlans } = await import("./colony-UGVYALOS.js");
617
+ const { getActivePlans } = await import("./colony-7GZ2ODF2.js");
618
618
  const plans = getActivePlans();
619
619
  return {
620
620
  content: [
@@ -637,7 +637,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
637
637
  },
638
638
  async ({ description }) => {
639
639
  try {
640
- const { proposePlan } = await import("./colony-UGVYALOS.js");
640
+ const { proposePlan } = await import("./colony-7GZ2ODF2.js");
641
641
  const result = await proposePlan(description);
642
642
  if (result.success) {
643
643
  return {
@@ -660,7 +660,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
660
660
  },
661
661
  async ({ planId }) => {
662
662
  try {
663
- const { joinPlan } = await import("./colony-UGVYALOS.js");
663
+ const { joinPlan } = await import("./colony-7GZ2ODF2.js");
664
664
  const result = await joinPlan(planId);
665
665
  if (result.success) {
666
666
  return { content: [{ type: "text", text: "Joined the plan! Go execute it." }] };
@@ -679,7 +679,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
679
679
  },
680
680
  async ({ status }) => {
681
681
  try {
682
- const { postStatus } = await import("./colony-UGVYALOS.js");
682
+ const { postStatus } = await import("./colony-7GZ2ODF2.js");
683
683
  const result = await postStatus(status);
684
684
  if (result.success) {
685
685
  return { content: [{ type: "text", text: "Status posted to the Colony." }] };
@@ -696,7 +696,7 @@ Identity saved. Your Spore is alive. Use spora_get_identity to read the full doc
696
696
  {},
697
697
  async () => {
698
698
  try {
699
- const { getTodaysActivity } = await import("./colony-UGVYALOS.js");
699
+ const { getTodaysActivity } = await import("./colony-7GZ2ODF2.js");
700
700
  const activity = getTodaysActivity();
701
701
  return {
702
702
  content: [
@@ -8,7 +8,7 @@ import {
8
8
  saveLearnings,
9
9
  saveRelationships,
10
10
  updateRelationship
11
- } from "./chunk-BBXHECZ5.js";
11
+ } from "./chunk-6WBIVXOY.js";
12
12
  import "./chunk-ZWKTKWS6.js";
13
13
  export {
14
14
  addLearning,
@@ -21,4 +21,4 @@ export {
21
21
  saveRelationships,
22
22
  updateRelationship
23
23
  };
24
- //# sourceMappingURL=memory-DTSLVSQG.js.map
24
+ //# sourceMappingURL=memory-G4DNIGLT.js.map
@@ -6,14 +6,14 @@ import {
6
6
  buildSystemPrompt,
7
7
  buildToolDecisionMessage,
8
8
  buildTrainingChatPrompt
9
- } from "./chunk-JIMONWKO.js";
9
+ } from "./chunk-73CWOI44.js";
10
10
  import "./chunk-OTZNHIXT.js";
11
11
  import "./chunk-CAWWG3MD.js";
12
- import "./chunk-CP6JWCLY.js";
12
+ import "./chunk-ER6TILYZ.js";
13
13
  import "./chunk-IULO3GRE.js";
14
14
  import "./chunk-RSNEVBEI.js";
15
15
  import "./chunk-QYFNAGNI.js";
16
- import "./chunk-BBXHECZ5.js";
16
+ import "./chunk-6WBIVXOY.js";
17
17
  import "./chunk-ZWKTKWS6.js";
18
18
  export {
19
19
  buildChatPrompt,
@@ -24,4 +24,4 @@ export {
24
24
  buildToolDecisionMessage,
25
25
  buildTrainingChatPrompt
26
26
  };
27
- //# sourceMappingURL=prompt-builder-ZFUZNQY2.js.map
27
+ //# sourceMappingURL=prompt-builder-S6PJVEC5.js.map
@@ -2,7 +2,7 @@ import {
2
2
  addToQueue,
3
3
  flushQueue,
4
4
  showQueue
5
- } from "./chunk-ZLSDFYBR.js";
5
+ } from "./chunk-AIGSCHZK.js";
6
6
  import "./chunk-RSNEVBEI.js";
7
7
  import "./chunk-QYFNAGNI.js";
8
8
  import "./chunk-ZWKTKWS6.js";
@@ -11,4 +11,4 @@ export {
11
11
  flushQueue,
12
12
  showQueue
13
13
  };
14
- //# sourceMappingURL=queue-QCGNDHH2.js.map
14
+ //# sourceMappingURL=queue-YPBUUP22.js.map
@@ -30,7 +30,7 @@
30
30
  }
31
31
 
32
32
  .logo-img {
33
- height: 28px;
33
+ height: 24px;
34
34
  width: auto;
35
35
  }
36
36
 
@@ -41,42 +41,26 @@
41
41
  gap: 0.75rem;
42
42
  }
43
43
 
44
- .status-dot {
45
- width: 7px;
46
- height: 7px;
47
- border-radius: 50%;
48
- background: #389e77;
49
- animation: pulse 2s infinite;
50
- }
51
-
52
- .status-text {
53
- font-size: 0.75rem;
54
- color: rgba(255, 255, 255, 0.4);
55
- }
56
-
57
44
  .heartbeat-menu {
58
45
  position: relative;
59
46
  }
60
47
 
61
48
  .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;
49
+ border: none;
50
+ background: transparent;
51
+ color: #fff;
68
52
  cursor: pointer;
69
- font-size: 0.95rem;
53
+ font-size: 1.05rem;
70
54
  line-height: 1;
71
55
  display: inline-flex;
72
56
  align-items: center;
73
57
  justify-content: center;
74
- transition: transform 0.15s ease, border-color 0.2s ease;
58
+ padding: 0;
59
+ transition: opacity 0.2s ease;
75
60
  }
76
61
 
77
62
  .heartbeat-trigger:hover {
78
- transform: scale(1.06);
79
- border-color: rgba(255, 100, 125, 0.45);
63
+ opacity: 0.72;
80
64
  }
81
65
 
82
66
  .heartbeat-trigger:disabled {
@@ -130,11 +114,6 @@
130
114
  color: #89dbba;
131
115
  }
132
116
 
133
- @keyframes pulse {
134
- 0%, 100% { opacity: 1; }
135
- 50% { opacity: 0.5; }
136
- }
137
-
138
117
  .chat-container {
139
118
  flex: 1;
140
119
  overflow-y: auto;
@@ -280,27 +259,27 @@
280
259
 
281
260
  .tweet-card-status.success { color: #389e77; }
282
261
 
283
- /* ===== Narration (agent thinking) ===== */
284
- .narration {
262
+ .live-status {
285
263
  display: flex;
286
264
  align-items: center;
287
- gap: 0.5rem;
265
+ gap: 0.45rem;
288
266
  padding: 0.4rem 0;
289
- font-size: 0.75rem;
290
- color: rgba(255, 255, 255, 0.3);
291
- animation: fadeIn 0.3s ease-in;
267
+ font-size: 0.74rem;
268
+ color: rgba(255, 255, 255, 0.42);
269
+ animation: fadeIn 0.2s ease-in;
292
270
  }
293
271
 
294
- .narration-dot {
272
+ .live-status::before {
273
+ content: '';
295
274
  width: 5px;
296
275
  height: 5px;
297
276
  border-radius: 50%;
298
- background: rgba(255, 255, 255, 0.2);
277
+ background: rgba(255, 255, 255, 0.28);
299
278
  flex-shrink: 0;
300
279
  }
301
280
 
302
- .narration-dot.action { background: #389e77; }
303
- .narration-dot.error { background: #e74c4c; }
281
+ .live-status.action::before { background: #389e77; }
282
+ .live-status.error::before { background: #e74c4c; }
304
283
 
305
284
  /* ===== Sleep State ===== */
306
285
  .sleep-card {
@@ -399,12 +378,8 @@
399
378
  </head>
400
379
  <body>
401
380
  <div class="header">
402
- <svg class="logo-img" viewBox="0 0 120 28" fill="none" xmlns="http://www.w3.org/2000/svg">
403
- <text x="0" y="22" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="22" font-weight="800" letter-spacing="-0.5" fill="#fff">spora</text>
404
- </svg>
381
+ <img class="logo-img" src="/logo2.png" alt="Spora" />
405
382
  <div class="header-right">
406
- <span class="status-dot"></span>
407
- <span class="status-text" id="statusText">Online</span>
408
383
  <div class="heartbeat-menu" id="heartbeatMenu">
409
384
  <button class="heartbeat-trigger" id="heartbeatTrigger" title="Heartbeat settings" aria-label="Heartbeat settings">♥</button>
410
385
  <div class="heartbeat-popover" id="heartbeatPopover" hidden>
@@ -441,7 +416,6 @@
441
416
  const heartbeatTrigger = document.getElementById('heartbeatTrigger');
442
417
  const heartbeatPopover = document.getElementById('heartbeatPopover');
443
418
  const heartbeatOptions = Array.from(document.querySelectorAll('.heartbeat-option'));
444
- const statusText = document.getElementById('statusText');
445
419
 
446
420
  let isLoading = false;
447
421
  let agentName = 'Your Spore';
@@ -462,10 +436,6 @@
462
436
  return HEARTBEAT_LABELS[ms] || `${Math.round(ms / 60000)} min`;
463
437
  }
464
438
 
465
- function updateStatusText() {
466
- statusText.textContent = `Online • ${heartbeatLabel(heartbeatMs)} heartbeat`;
467
- }
468
-
469
439
  function setHeartbeatPopoverOpen(isOpen) {
470
440
  heartbeatPopover.hidden = !isOpen;
471
441
  }
@@ -484,7 +454,6 @@
484
454
  const data = await response.json();
485
455
  if (data.intervalMs) {
486
456
  heartbeatMs = data.intervalMs;
487
- updateStatusText();
488
457
  updateHeartbeatOptions();
489
458
  }
490
459
  } catch {
@@ -506,13 +475,12 @@
506
475
  throw new Error('Failed to update heartbeat');
507
476
  }
508
477
  heartbeatMs = intervalMs;
509
- updateStatusText();
510
478
  updateHeartbeatOptions();
511
- addNarration(`Heartbeat updated to ${heartbeatLabel(intervalMs)}`, 'action');
479
+ setLiveStatus(`heartbeat set to ${heartbeatLabel(intervalMs)}`, 'action');
512
480
  } catch {
513
481
  heartbeatMs = previous;
514
482
  updateHeartbeatOptions();
515
- addNarration('Could not update heartbeat interval', 'error');
483
+ setLiveStatus('could not update heartbeat', 'error');
516
484
  } finally {
517
485
  heartbeatTrigger.disabled = false;
518
486
  }
@@ -570,6 +538,7 @@
570
538
  }
571
539
 
572
540
  function addMessage(role, content, animate = true) {
541
+ clearLiveStatus();
573
542
  removeSleepCard();
574
543
  const messageDiv = document.createElement('div');
575
544
  messageDiv.className = `message ${role}`;
@@ -589,8 +558,12 @@
589
558
  }
590
559
 
591
560
  // ===== Tweet Card =====
592
- function addTweetCard(text, status) {
561
+ function addTweetCard(text, meta = {}) {
562
+ clearLiveStatus();
593
563
  removeSleepCard();
564
+ const kind = typeof meta.kind === 'string' ? meta.kind : 'Post';
565
+ const status = typeof meta.status === 'string' ? meta.status : '';
566
+ const replyTo = typeof meta.replyTo === 'string' ? meta.replyTo : '';
594
567
  const card = document.createElement('div');
595
568
  card.className = 'tweet-card';
596
569
 
@@ -611,7 +584,7 @@
611
584
 
612
585
  const badge = document.createElement('span');
613
586
  badge.className = 'tweet-card-badge';
614
- badge.textContent = 'Posted';
587
+ badge.textContent = kind;
615
588
 
616
589
  header.appendChild(avatar);
617
590
  header.appendChild(author);
@@ -627,12 +600,19 @@
627
600
 
628
601
  card.appendChild(header);
629
602
  card.appendChild(body);
603
+ if (replyTo) {
604
+ const replyToEl = document.createElement('div');
605
+ replyToEl.className = 'tweet-card-time';
606
+ replyToEl.textContent = `replying to ${replyTo}`;
607
+ card.appendChild(replyToEl);
608
+ }
630
609
  card.appendChild(time);
631
610
 
632
611
  if (status) {
633
612
  const statusEl = document.createElement('div');
634
- statusEl.className = `tweet-card-status ${status === 'success' ? 'success' : ''}`;
635
- statusEl.textContent = status === 'success' ? 'Posted to X' : status;
613
+ const successLike = /posted|sent|queued|success/i.test(status);
614
+ statusEl.className = `tweet-card-status ${successLike ? 'success' : ''}`;
615
+ statusEl.textContent = status;
636
616
  card.appendChild(statusEl);
637
617
  }
638
618
 
@@ -641,15 +621,24 @@
641
621
  }
642
622
 
643
623
  // ===== Narration =====
644
- function addNarration(text, type) {
645
- const el = document.createElement('div');
646
- el.className = 'narration';
647
- const dot = document.createElement('div');
648
- dot.className = `narration-dot ${type || ''}`;
649
- const span = document.createElement('span');
650
- span.textContent = text;
651
- el.appendChild(dot);
652
- el.appendChild(span);
624
+ function clearLiveStatus() {
625
+ const existing = document.getElementById('liveStatus');
626
+ if (existing) existing.remove();
627
+ }
628
+
629
+ function setLiveStatus(text, type) {
630
+ if (!text) {
631
+ clearLiveStatus();
632
+ return;
633
+ }
634
+ removeSleepCard();
635
+ let el = document.getElementById('liveStatus');
636
+ if (!el) {
637
+ el = document.createElement('div');
638
+ el.id = 'liveStatus';
639
+ }
640
+ el.className = `live-status ${type || ''}`;
641
+ el.textContent = text;
653
642
  chatContainer.appendChild(el);
654
643
  chatContainer.scrollTop = chatContainer.scrollHeight;
655
644
  }
@@ -803,23 +792,27 @@
803
792
  function renderAgentEvent(event) {
804
793
  switch (event.type) {
805
794
  case 'narration':
806
- addNarration(event.content);
795
+ setLiveStatus(event.content);
796
+ break;
797
+ case 'summary':
798
+ addMessage('assistant', event.content);
807
799
  break;
808
800
  case 'tweet':
809
- addTweetCard(event.content, event.meta?.status);
801
+ addTweetCard(event.content, event.meta || {});
810
802
  break;
811
803
  case 'action':
812
- addNarration(event.content, 'action');
804
+ setLiveStatus(event.content, 'action');
813
805
  break;
814
806
  case 'error':
815
- addNarration(event.content, 'error');
807
+ setLiveStatus(event.content, 'error');
816
808
  break;
817
809
  case 'sleep':
810
+ clearLiveStatus();
818
811
  showSleepState(event.meta?.wakeUpAt || (Date.now() + 300000));
819
812
  break;
820
813
  case 'wake':
821
814
  removeSleepCard();
822
- addNarration(event.content);
815
+ setLiveStatus(event.content);
823
816
  break;
824
817
  }
825
818
  }
@@ -827,7 +820,6 @@
827
820
  setInterval(pollAgentEvents, 2000);
828
821
 
829
822
  // Initialize
830
- updateStatusText();
831
823
  updateHeartbeatOptions();
832
824
  init();
833
825
  messageInput.focus();
Binary file