spora 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,9 +8,10 @@ import {
8
8
  buildOpportunityPortfolioMessage,
9
9
  buildPersonaConstraintLines,
10
10
  buildSystemPrompt,
11
+ compilePersonaActionProfile,
11
12
  getPersonaConstraints,
12
13
  personaConstraintHandles
13
- } from "./chunk-E5PEY36J.js";
14
+ } from "./chunk-JIMONWKO.js";
14
15
  import {
15
16
  listIntents,
16
17
  loadStrategy,
@@ -783,8 +784,9 @@ function evaluateActionPolicy(context) {
783
784
  }
784
785
  }
785
786
  if (action.action === "reply" && action.tweetId) {
787
+ const inStrictTargetMode = strictReplyHandles.size > 0 || strictInteractHandles.size > 0;
786
788
  const repliesThisHeartbeat = executedActions.filter((a) => a.action === "reply").length;
787
- if (repliesThisHeartbeat >= 2) {
789
+ if (repliesThisHeartbeat >= 2 && !inStrictTargetMode) {
788
790
  return {
789
791
  allowed: false,
790
792
  reason: "Reply cap reached for this heartbeat. Use another tool action."
@@ -795,7 +797,7 @@ function evaluateActionPolicy(context) {
795
797
  const repliedAuthors = new Set(
796
798
  executedActions.filter((a) => a.action === "reply" && a.tweetId).map((a) => normalizeHandle(tweetById.get(a.tweetId)?.authorHandle)).filter(Boolean)
797
799
  );
798
- if (repliedAuthors.has(currentAuthor)) {
800
+ if (repliedAuthors.has(currentAuthor) && !inStrictTargetMode) {
799
801
  return {
800
802
  allowed: false,
801
803
  reason: `Already replied to @${currentAuthor} this heartbeat. Diversify interactions.`
@@ -1020,6 +1022,9 @@ function shuffle(items) {
1020
1022
  }
1021
1023
  return arr;
1022
1024
  }
1025
+ function clampInt(value, min, max) {
1026
+ return Math.max(min, Math.min(max, Math.round(value)));
1027
+ }
1023
1028
  function buildTopicQueryVariants(topic) {
1024
1029
  const trimmed = topic.trim();
1025
1030
  if (!trimmed) return [];
@@ -1244,12 +1249,17 @@ async function runTopicSearch(client, heartbeatCount, seedTweets) {
1244
1249
  const constraints = getPersonaConstraints(identity);
1245
1250
  const strictHandles = personaConstraintHandles(constraints).filter(Boolean);
1246
1251
  const strategy = loadStrategy();
1252
+ const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
1247
1253
  const selfHandle = canonicalAgentHandle(identity.handle);
1254
+ const topicSearchBias = personaProfile.sourceBias.topic_search;
1255
+ const peopleWatchBias = personaProfile.sourceBias.people_watch;
1248
1256
  const allTopics = [
1257
+ ...personaProfile.priorityTopics,
1249
1258
  ...identity.topics ?? [],
1250
1259
  ...strategy.currentFocus
1251
1260
  ].flatMap(splitTopicSignal).filter(Boolean);
1252
- const discovered = discoverQueriesFromTweets(seedTweets, 3);
1261
+ const discoveredBudget = clampInt(1 + Math.max(0, topicSearchBias), 1, 3);
1262
+ const discovered = discoverQueriesFromTweets(seedTweets, discoveredBudget);
1253
1263
  const targetHandles = [.../* @__PURE__ */ new Set([
1254
1264
  ...strategy.peopleToEngage.filter((person) => person.priority === "high" || person.priority === "medium").map((person) => person.handle.replace(/^@/, "").toLowerCase()),
1255
1265
  ...(identity.heroes ?? []).map((handle) => handle.replace(/^@/, "").toLowerCase())
@@ -1257,11 +1267,16 @@ async function runTopicSearch(client, heartbeatCount, seedTweets) {
1257
1267
  const strictQueries = buildTargetedPersonQueries(
1258
1268
  strictHandles.filter((handle) => handle !== selfHandle),
1259
1269
  heartbeatCount,
1260
- 3
1270
+ clampInt(2 + Math.max(0, topicSearchBias), 1, 4)
1271
+ );
1272
+ const personQueries = buildTargetedPersonQueries(
1273
+ targetHandles,
1274
+ heartbeatCount,
1275
+ clampInt(1 + Math.max(0, peopleWatchBias), 0, 3)
1261
1276
  );
1262
- const personQueries = buildTargetedPersonQueries(targetHandles, heartbeatCount, 2);
1263
- const topicQueries = allTopics.length > 0 ? pickTopicQueries(allTopics, Math.min(3, Math.max(2, allTopics.length))) : [];
1264
- const discoveredQueries = discovered.length > 0 ? pickDirectQueries(discovered, Math.min(2, discovered.length)) : [];
1277
+ const topicBudget = clampInt(3 + topicSearchBias, 1, 5);
1278
+ const topicQueries = allTopics.length > 0 ? pickTopicQueries(allTopics, Math.min(topicBudget, Math.max(1, allTopics.length))) : [];
1279
+ const discoveredQueries = discovered.length > 0 ? pickDirectQueries(discovered, Math.min(discoveredBudget, discovered.length)) : [];
1265
1280
  const topicsToSearch = strictQueries.length > 0 ? [...new Set(strictQueries)] : [.../* @__PURE__ */ new Set([...topicQueries, ...discoveredQueries, ...personQueries])];
1266
1281
  if (topicsToSearch.length === 0) return [];
1267
1282
  const results = [];
@@ -1287,6 +1302,7 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
1287
1302
  const strategy = loadStrategy();
1288
1303
  const identity = loadIdentity();
1289
1304
  const constraints = getPersonaConstraints(identity);
1305
+ const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
1290
1306
  const strictHandles = personaConstraintHandles(constraints).map((handle) => canonicalAgentHandle(handle)).filter((handle) => handle.length > 0);
1291
1307
  const selfHandle = identity.handle.replace(/^@/, "").toLowerCase();
1292
1308
  const relationships = loadRelationships();
@@ -1335,9 +1351,10 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
1335
1351
  for (const p of strategy.peopleToEngage.filter((p2) => p2.priority === "high")) {
1336
1352
  addPerson(p.handle, p.reason, true);
1337
1353
  }
1354
+ const sporaPinInterval = personaProfile.sourceBias.people_watch >= 0.6 ? 4 : 8;
1338
1355
  for (const handle of networkHandles) {
1339
1356
  if (handle === "sporaai") {
1340
- const pinSpora = heartbeatCount % 4 === 0;
1357
+ const pinSpora = heartbeatCount % sporaPinInterval === 0;
1341
1358
  addPerson(handle, "core Spora profile", pinSpora);
1342
1359
  } else {
1343
1360
  addPerson(handle, "spora agent network");
@@ -1362,7 +1379,8 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
1362
1379
  addPerson(handle, "active voice in current conversations");
1363
1380
  }
1364
1381
  if (people.length === 0) return [];
1365
- const budget = Math.min(3, people.length);
1382
+ const monitoringBudget = clampInt(3 + personaProfile.sourceBias.people_watch, 2, 5);
1383
+ const budget = Math.min(monitoringBudget, people.length);
1366
1384
  const selected = [];
1367
1385
  const selectedHandles = /* @__PURE__ */ new Set();
1368
1386
  const pinned = people.filter((person) => person.pinned);
@@ -1464,6 +1482,7 @@ function buildPersonaContext() {
1464
1482
  const identity = loadIdentity();
1465
1483
  const constraints = getPersonaConstraints(identity);
1466
1484
  const strategy = loadStrategy();
1485
+ const actionProfile = compilePersonaActionProfile({ identity, strategy, constraints });
1467
1486
  const focusKeywords = unique(
1468
1487
  [
1469
1488
  ...identity.topics,
@@ -1486,7 +1505,22 @@ function buildPersonaContext() {
1486
1505
  (identity.heroes ?? []).map((handle) => normalizeHandle3(handle)).filter(Boolean)
1487
1506
  );
1488
1507
  const networkHandles = new Set(listAgentNetworkHandles(60).map((handle) => normalizeHandle3(handle)).filter(Boolean));
1489
- return { focusKeywords, avoidKeywords, priorityHandles, heroHandles, networkHandles };
1508
+ return { focusKeywords, avoidKeywords, priorityHandles, heroHandles, networkHandles, actionProfile };
1509
+ }
1510
+ function personaActionDelta(actionType, source, profile) {
1511
+ return profile.actionBias[actionType] * 0.9 + profile.sourceBias[source] * 0.55;
1512
+ }
1513
+ function isActionStronglySuppressed(actionType, profile) {
1514
+ return profile.actionBias[actionType] <= -1.35;
1515
+ }
1516
+ function matchesPersonaGeneratedProfile(actionType, source, profile) {
1517
+ const actionBias = profile.actionBias[actionType];
1518
+ const sourceBias = profile.sourceBias[source];
1519
+ const combined = personaActionDelta(actionType, source, profile);
1520
+ if (actionBias <= -0.55) return false;
1521
+ if (sourceBias <= -1.05) return false;
1522
+ if (combined <= -0.3) return false;
1523
+ return true;
1490
1524
  }
1491
1525
  function phraseMatchScore(text, keywords) {
1492
1526
  if (keywords.length === 0) return 0;
@@ -1591,6 +1625,7 @@ function buildActionOpportunities(input) {
1591
1625
  const onlyReplyHandles = new Set(constraints.onlyReplyToHandles.map((h) => normalizeHandle3(h)));
1592
1626
  const onlyInteractHandles = new Set(constraints.onlyInteractWithHandles.map((h) => normalizeHandle3(h)));
1593
1627
  const persona = buildPersonaContext();
1628
+ const actionProfile = persona.actionProfile;
1594
1629
  const recent = getRecentInteractions(300);
1595
1630
  const {
1596
1631
  repliedTweetIds,
@@ -1638,27 +1673,33 @@ function buildActionOpportunities(input) {
1638
1673
  persona
1639
1674
  );
1640
1675
  const shortTweet = clip(tweet.text, 170);
1641
- opportunities.push({
1642
- id: nextId(),
1643
- armKey: armKey("reply", source),
1644
- actionType: "reply",
1645
- source,
1646
- score: baseScore + 0.7,
1647
- summary: `Reply to @${handle} from ${source}`,
1648
- authorHandle: handle,
1649
- tweetId: tweet.id,
1650
- requiresContent: true,
1651
- template: { action: "reply", tweetId: tweet.id, source, targetHandle: `@${handle}` },
1652
- context: `@${handle}: "${shortTweet}"`
1653
- });
1676
+ const replyScore = baseScore + 0.7 + personaActionDelta("reply", source, actionProfile);
1677
+ const likeScore = baseScore + 0.2 + personaActionDelta("like", source, actionProfile);
1678
+ const retweetScore = baseScore - 0.1 + personaActionDelta("retweet", source, actionProfile);
1679
+ const followScore = baseScore - 0.2 + personaActionDelta("follow", source, actionProfile);
1680
+ if (!isActionStronglySuppressed("reply", actionProfile) && matchesPersonaGeneratedProfile("reply", source, actionProfile)) {
1681
+ opportunities.push({
1682
+ id: nextId(),
1683
+ armKey: armKey("reply", source),
1684
+ actionType: "reply",
1685
+ source,
1686
+ score: replyScore,
1687
+ summary: `Reply to @${handle} from ${source}`,
1688
+ authorHandle: handle,
1689
+ tweetId: tweet.id,
1690
+ requiresContent: true,
1691
+ template: { action: "reply", tweetId: tweet.id, source, targetHandle: `@${handle}` },
1692
+ context: `@${handle}: "${shortTweet}"`
1693
+ });
1694
+ }
1654
1695
  const stronglyRelevant = alignment >= 0.35 || source === "mention" || source === "people_watch" || persona.priorityHandles.has(handle);
1655
- if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !likedTweetIds.has(tweet.id) && baseScore >= 1.8 && stronglyRelevant) {
1696
+ if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !isActionStronglySuppressed("like", actionProfile) && matchesPersonaGeneratedProfile("like", source, actionProfile) && !likedTweetIds.has(tweet.id) && likeScore >= 1.8 && stronglyRelevant) {
1656
1697
  opportunities.push({
1657
1698
  id: nextId(),
1658
1699
  armKey: armKey("like", source),
1659
1700
  actionType: "like",
1660
1701
  source,
1661
- score: baseScore + 0.2,
1702
+ score: likeScore,
1662
1703
  summary: `Like @${handle} from ${source}`,
1663
1704
  authorHandle: handle,
1664
1705
  tweetId: tweet.id,
@@ -1667,13 +1708,13 @@ function buildActionOpportunities(input) {
1667
1708
  context: `@${handle}: "${shortTweet}"`
1668
1709
  });
1669
1710
  }
1670
- if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !retweetedTweetIds.has(tweet.id) && baseScore >= 2.6 && alignment >= 0.15) {
1711
+ if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !isActionStronglySuppressed("retweet", actionProfile) && matchesPersonaGeneratedProfile("retweet", source, actionProfile) && !retweetedTweetIds.has(tweet.id) && retweetScore >= 2.6 && alignment >= 0.15) {
1671
1712
  opportunities.push({
1672
1713
  id: nextId(),
1673
1714
  armKey: armKey("retweet", source),
1674
1715
  actionType: "retweet",
1675
1716
  source,
1676
- score: baseScore - 0.1,
1717
+ score: retweetScore,
1677
1718
  summary: `Retweet @${handle} from ${source}`,
1678
1719
  authorHandle: handle,
1679
1720
  tweetId: tweet.id,
@@ -1682,7 +1723,7 @@ function buildActionOpportunities(input) {
1682
1723
  context: `@${handle}: "${shortTweet}"`
1683
1724
  });
1684
1725
  }
1685
- const shouldConsiderFollow = onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !followedHandles.has(handle) && !followOpportunityByHandle.has(handle) && !isLikelySyntheticHandle2(handle) && baseScore >= 3 && (persona.priorityHandles.has(handle) || persona.heroHandles.has(handle) || source === "mention" || alignment >= 0.8);
1726
+ const shouldConsiderFollow = onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !isActionStronglySuppressed("follow", actionProfile) && matchesPersonaGeneratedProfile("follow", source, actionProfile) && !followedHandles.has(handle) && !followOpportunityByHandle.has(handle) && !isLikelySyntheticHandle2(handle) && followScore >= 3 && (persona.priorityHandles.has(handle) || persona.heroHandles.has(handle) || source === "mention" || alignment >= 0.8);
1686
1727
  if (shouldConsiderFollow) {
1687
1728
  followOpportunityByHandle.add(handle);
1688
1729
  opportunities.push({
@@ -1690,7 +1731,7 @@ function buildActionOpportunities(input) {
1690
1731
  armKey: armKey("follow", source),
1691
1732
  actionType: "follow",
1692
1733
  source,
1693
- score: baseScore - 0.2,
1734
+ score: followScore,
1694
1735
  summary: `Follow @${handle}`,
1695
1736
  authorHandle: handle,
1696
1737
  requiresContent: false,
@@ -1700,17 +1741,19 @@ function buildActionOpportunities(input) {
1700
1741
  }
1701
1742
  }
1702
1743
  const queryList = research.topicSearchResults.map((r) => r.query).filter(Boolean);
1703
- if (queryList.length > 0 && !constraints.noOriginalPosts && !constraints.replyOnlyMode && onlyInteractHandles.size === 0 && onlyReplyHandles.size === 0) {
1744
+ if (queryList.length > 0 && !constraints.noOriginalPosts && !constraints.replyOnlyMode && !isActionStronglySuppressed("post", actionProfile) && matchesPersonaGeneratedProfile("post", "synthesis", actionProfile) && onlyInteractHandles.size === 0 && onlyReplyHandles.size === 0) {
1745
+ const synthesisScore = 2.25 + personaActionDelta("post", "synthesis", actionProfile);
1746
+ const priorityTopicHint = actionProfile.priorityTopics.slice(0, 3).join(", ");
1704
1747
  opportunities.push({
1705
1748
  id: nextId(),
1706
1749
  armKey: armKey("post", "synthesis"),
1707
1750
  actionType: "post",
1708
1751
  source: "synthesis",
1709
- score: 2.25,
1752
+ score: synthesisScore,
1710
1753
  summary: "Post an original thought synthesized from active conversations",
1711
1754
  requiresContent: true,
1712
1755
  template: { action: "post", source: "synthesis" },
1713
- context: `Weave a fresh take from active topics: ${queryList.slice(0, 4).join(", ")}`
1756
+ context: `Weave a fresh take from active topics: ${queryList.slice(0, 4).join(", ")}${priorityTopicHint ? `. Persona-priority topics: ${priorityTopicHint}` : ""}`
1714
1757
  });
1715
1758
  }
1716
1759
  opportunities.sort((a, b) => b.score - a.score);
@@ -1783,6 +1826,202 @@ async function planActionPortfolio(input) {
1783
1826
  return actions;
1784
1827
  }
1785
1828
 
1829
+ // src/runtime/mission-director.ts
1830
+ function normalizeHandle4(handle) {
1831
+ return handle.replace(/^@/, "").trim().toLowerCase();
1832
+ }
1833
+ function unique2(values) {
1834
+ return [...new Set(values)];
1835
+ }
1836
+ function isAction(value) {
1837
+ return ["reply", "like", "retweet", "follow", "post"].includes(value);
1838
+ }
1839
+ function sanitizeActions(raw, fallback) {
1840
+ if (!Array.isArray(raw) || raw.length === 0) return fallback;
1841
+ const cleaned = raw.map((item) => String(item).trim().toLowerCase()).filter((item) => isAction(item));
1842
+ return cleaned.length > 0 ? unique2(cleaned) : fallback;
1843
+ }
1844
+ function parseJsonObject(text) {
1845
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
1846
+ const candidates = [fenced, text].filter((v) => Boolean(v)).map((v) => v.trim());
1847
+ for (const candidate of candidates) {
1848
+ try {
1849
+ return JSON.parse(candidate);
1850
+ } catch {
1851
+ }
1852
+ const obj = candidate.match(/\{[\s\S]*\}/);
1853
+ if (!obj) continue;
1854
+ try {
1855
+ return JSON.parse(obj[0]);
1856
+ } catch {
1857
+ }
1858
+ }
1859
+ return null;
1860
+ }
1861
+ function buildPersonaAllowedActions(profile, noOriginalPosts) {
1862
+ const byBias = profile.prioritizedActions.filter((action) => profile.actionBias[action] > -1.1);
1863
+ const withFallback = byBias.length > 0 ? byBias : ["reply", "like", "retweet", "follow", "post"];
1864
+ const filtered = noOriginalPosts ? withFallback.filter((action) => action !== "post") : withFallback;
1865
+ return unique2(filtered).slice(0, 5);
1866
+ }
1867
+ function buildDeterministicMission(research, profile) {
1868
+ const identity = loadIdentity();
1869
+ const strategy = loadStrategy();
1870
+ const constraints = getPersonaConstraints(identity);
1871
+ const constraintTargets = personaConstraintHandles(constraints).map(normalizeHandle4).filter(Boolean);
1872
+ const focusTopics = unique2(
1873
+ [
1874
+ ...profile.priorityTopics,
1875
+ ...identity.topics ?? [],
1876
+ ...strategy.currentFocus,
1877
+ ...strategy.shortTermGoals
1878
+ ].map((topic) => String(topic).trim()).filter(Boolean).slice(0, 10)
1879
+ );
1880
+ const strictReply = constraints.onlyReplyToHandles.length > 0 || constraints.replyOnlyMode;
1881
+ if (strictReply) {
1882
+ const strictTargets = constraints.onlyReplyToHandles.length > 0 ? constraints.onlyReplyToHandles.map(normalizeHandle4) : constraintTargets;
1883
+ return {
1884
+ mode: "strict_target_reply",
1885
+ objective: strictTargets.length > 0 ? `Reply only to @${strictTargets.join(", @")} while staying fully in character.` : "Reply-only mode. Stay in character and do not perform non-reply actions.",
1886
+ allowedActions: ["reply"],
1887
+ targetHandles: strictTargets,
1888
+ focusTopics,
1889
+ rationale: "Hard persona constraint detected in identity goals/boundaries/bio."
1890
+ };
1891
+ }
1892
+ if (constraintTargets.length > 0) {
1893
+ return {
1894
+ mode: "strict_target_interaction",
1895
+ objective: `Interact only with @${constraintTargets.join(", @")} in persona voice.`,
1896
+ allowedActions: constraints.noOriginalPosts ? ["reply", "like", "retweet", "follow"] : ["reply", "like", "retweet", "follow", "post"],
1897
+ targetHandles: constraintTargets,
1898
+ focusTopics,
1899
+ rationale: "Hard persona interaction target detected."
1900
+ };
1901
+ }
1902
+ const preferredActions = buildPersonaAllowedActions(profile, constraints.noOriginalPosts);
1903
+ const primaryActionHint = preferredActions.length > 0 ? preferredActions.slice(0, 2).join(" + ") : "context-aware interactions";
1904
+ return {
1905
+ mode: "persona_balanced",
1906
+ objective: `Advance ${identity.name}'s mission through ${primaryActionHint} while staying in strict persona voice.`,
1907
+ allowedActions: preferredActions,
1908
+ targetHandles: [],
1909
+ focusTopics,
1910
+ rationale: `No strict target lock; persona profile drives soft action/source priorities (${profile.rationale.slice(0, 2).join("; ")}).`
1911
+ };
1912
+ }
1913
+ function buildMissionPrompt(research, fallback, profile) {
1914
+ const identity = loadIdentity();
1915
+ const constraints = getPersonaConstraints(identity);
1916
+ const topTimeline = research.timeline.slice(0, 5);
1917
+ const topMentions = research.mentions.slice(0, 5);
1918
+ const topicQueries = research.topicSearchResults.map((r) => r.query).filter(Boolean).slice(0, 6);
1919
+ const people = research.peopleActivity.map((p) => p.handle).filter(Boolean).slice(0, 6);
1920
+ const lines = [];
1921
+ lines.push("Create one heartbeat mission JSON for this agent.");
1922
+ lines.push("Hard rule: Persona constraints override everything.");
1923
+ lines.push("");
1924
+ lines.push(`Agent: ${identity.name} (@${identity.handle})`);
1925
+ lines.push(`Bio: ${identity.bio}`);
1926
+ lines.push(`Tone: ${identity.tone}`);
1927
+ lines.push(`Goals: ${(identity.goals ?? []).join(" | ")}`);
1928
+ lines.push(`Boundaries: ${(identity.boundaries ?? []).join(" | ")}`);
1929
+ lines.push(`Framework: ${identity.framework}`);
1930
+ lines.push(`Existing strict constraints: ${JSON.stringify(constraints)}`);
1931
+ lines.push(`Persona action priors: ${JSON.stringify({
1932
+ prioritizedActions: profile.prioritizedActions.slice(0, 5),
1933
+ priorityTopics: profile.priorityTopics.slice(0, 8),
1934
+ actionBias: profile.actionBias,
1935
+ sourceBias: profile.sourceBias,
1936
+ rationale: profile.rationale.slice(0, 5)
1937
+ })}`);
1938
+ lines.push("");
1939
+ lines.push(`Fallback mission if unsure: ${JSON.stringify(fallback)}`);
1940
+ lines.push("");
1941
+ lines.push(`Observed timeline sample (${topTimeline.length}):`);
1942
+ for (const tweet of topTimeline) {
1943
+ lines.push(`- @${tweet.authorHandle}: ${tweet.text.slice(0, 180)}`);
1944
+ }
1945
+ lines.push(`Observed mentions sample (${topMentions.length}):`);
1946
+ for (const tweet of topMentions) {
1947
+ lines.push(`- @${tweet.authorHandle}: ${tweet.text.slice(0, 180)}`);
1948
+ }
1949
+ lines.push(`Topic queries: ${topicQueries.join(" | ") || "none"}`);
1950
+ lines.push(`People watched: ${people.map((h) => `@${h}`).join(", ") || "none"}`);
1951
+ lines.push("");
1952
+ lines.push("Return JSON only with this shape:");
1953
+ lines.push("{");
1954
+ lines.push(' "mode": "strict_target_reply|strict_target_interaction|persona_balanced",');
1955
+ lines.push(' "objective": "one-sentence mission",');
1956
+ lines.push(' "allowedActions": ["reply","like","retweet","follow","post"],');
1957
+ lines.push(' "targetHandles": ["grok"],');
1958
+ lines.push(' "focusTopics": ["topic a","topic b"],');
1959
+ lines.push(' "rationale": "short why"');
1960
+ lines.push("}");
1961
+ return lines.join("\n");
1962
+ }
1963
+ function mergeMission(fallback, parsed) {
1964
+ if (!parsed) return fallback;
1965
+ const mode = parsed.mode === "strict_target_reply" || parsed.mode === "strict_target_interaction" || parsed.mode === "persona_balanced" ? parsed.mode : fallback.mode;
1966
+ const objective = typeof parsed.objective === "string" && parsed.objective.trim().length > 0 ? parsed.objective.trim() : fallback.objective;
1967
+ const rationale = typeof parsed.rationale === "string" && parsed.rationale.trim().length > 0 ? parsed.rationale.trim() : fallback.rationale;
1968
+ const focusTopics = Array.isArray(parsed.focusTopics) ? unique2(
1969
+ parsed.focusTopics.map((topic) => String(topic).trim()).filter(Boolean).slice(0, 10)
1970
+ ) : fallback.focusTopics;
1971
+ const targetHandles = Array.isArray(parsed.targetHandles) ? unique2(
1972
+ parsed.targetHandles.map((handle) => normalizeHandle4(String(handle))).filter(Boolean)
1973
+ ) : fallback.targetHandles;
1974
+ let allowedActions = sanitizeActions(parsed.allowedActions, fallback.allowedActions);
1975
+ const mustReplyOnly = mode === "strict_target_reply";
1976
+ if (mustReplyOnly) {
1977
+ allowedActions = ["reply"];
1978
+ }
1979
+ const hasStrictTargets = fallback.targetHandles.length > 0;
1980
+ if (hasStrictTargets) {
1981
+ return {
1982
+ ...fallback,
1983
+ mode: mustReplyOnly ? "strict_target_reply" : mode,
1984
+ objective,
1985
+ rationale,
1986
+ focusTopics,
1987
+ allowedActions,
1988
+ targetHandles: fallback.targetHandles
1989
+ };
1990
+ }
1991
+ return {
1992
+ mode,
1993
+ objective,
1994
+ rationale,
1995
+ focusTopics,
1996
+ allowedActions,
1997
+ targetHandles
1998
+ };
1999
+ }
2000
+ async function createHeartbeatMission(research) {
2001
+ const identity = loadIdentity();
2002
+ const strategy = loadStrategy();
2003
+ const constraints = getPersonaConstraints(identity);
2004
+ const profile = compilePersonaActionProfile({ identity, strategy, constraints });
2005
+ const fallback = buildDeterministicMission(research, profile);
2006
+ if (fallback.mode !== "persona_balanced") {
2007
+ return fallback;
2008
+ }
2009
+ const system = [
2010
+ "You are a mission director for an autonomous X agent.",
2011
+ "Your output must be strict JSON only.",
2012
+ "Prioritize persona fidelity over engagement.",
2013
+ "Never violate explicit persona constraints."
2014
+ ].join(" ");
2015
+ try {
2016
+ const prompt = buildMissionPrompt(research, fallback, profile);
2017
+ const response = await generateResponse(system, prompt);
2018
+ const parsed = parseJsonObject(response.content);
2019
+ return mergeMission(fallback, parsed);
2020
+ } catch {
2021
+ return fallback;
2022
+ }
2023
+ }
2024
+
1786
2025
  // src/runtime/bandit.ts
1787
2026
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1788
2027
  var DEFAULT_EXPLORATION_BUDGET = 0.25;
@@ -2027,7 +2266,7 @@ function selfUserIdFromCredentials() {
2027
2266
  return null;
2028
2267
  }
2029
2268
  }
2030
- function normalizeHandle4(handle) {
2269
+ function normalizeHandle5(handle) {
2031
2270
  return (handle ?? "").replace(/^@/, "").trim().toLowerCase();
2032
2271
  }
2033
2272
  function roughWordCount(text) {
@@ -2097,6 +2336,64 @@ function shouldAttemptStyleRewrite(action, reason) {
2097
2336
  const lower = reason.toLowerCase();
2098
2337
  return lower.includes("lecture-like") || lower.includes("abstract philosopher cadence") || lower.includes("manifesto") || lower.includes("too long/explanatory") || lower.includes("grounded in the target tweet context") || lower.includes("hedged consensus phrasing") || lower.includes("repetitive anchor phrase/term") || lower.includes("repetitive opening phrase") || lower.includes("low-novelty vocabulary loop") || lower.includes("abstract wording without concrete anchor");
2099
2338
  }
2339
+ function personaAdjustedOpportunityScore(opportunity, profile) {
2340
+ const actionType = opportunity.actionType;
2341
+ const source = opportunity.source;
2342
+ const actionBias = profile.actionBias[actionType] ?? 0;
2343
+ const sourceBias = profile.sourceBias[source] ?? 0;
2344
+ return opportunity.score + actionBias * 0.9 + sourceBias * 0.55;
2345
+ }
2346
+ function buildPersonaGeneratedPool(opportunities, maxSize, profile) {
2347
+ if (opportunities.length <= 1) return opportunities.slice(0, maxSize);
2348
+ const maxPool = Math.max(1, Math.min(maxSize, opportunities.length));
2349
+ const byAction = /* @__PURE__ */ new Map();
2350
+ for (const action of ["reply", "like", "retweet", "follow", "post"]) {
2351
+ byAction.set(action, []);
2352
+ }
2353
+ for (const opportunity of opportunities) {
2354
+ const action = opportunity.actionType;
2355
+ byAction.get(action)?.push(opportunity);
2356
+ }
2357
+ for (const [action, rows] of byAction.entries()) {
2358
+ rows.sort((a, b) => personaAdjustedOpportunityScore(b, profile) - personaAdjustedOpportunityScore(a, profile));
2359
+ byAction.set(action, rows);
2360
+ }
2361
+ const actionOrder = profile.prioritizedActions.filter((action) => (byAction.get(action)?.length ?? 0) > 0);
2362
+ const weightedActions = actionOrder.map((action) => ({
2363
+ action,
2364
+ weight: Math.max(0.2, profile.actionBias[action] + 1.25)
2365
+ })).filter((row) => row.weight > 0);
2366
+ const selected = /* @__PURE__ */ new Map();
2367
+ if (weightedActions.length > 0) {
2368
+ const totalWeight = weightedActions.reduce((sum, row) => sum + row.weight, 0);
2369
+ const quotas = /* @__PURE__ */ new Map();
2370
+ for (const row of weightedActions) {
2371
+ quotas.set(row.action, Math.max(0, Math.floor(row.weight / totalWeight * maxPool)));
2372
+ }
2373
+ for (const topAction of actionOrder.slice(0, 2)) {
2374
+ const rows = byAction.get(topAction) ?? [];
2375
+ if (rows.length === 0) continue;
2376
+ quotas.set(topAction, Math.max(1, quotas.get(topAction) ?? 0));
2377
+ }
2378
+ for (const row of weightedActions) {
2379
+ const rows = byAction.get(row.action) ?? [];
2380
+ const limit = quotas.get(row.action) ?? 0;
2381
+ for (const candidate of rows.slice(0, limit)) {
2382
+ if (selected.size >= maxPool) break;
2383
+ selected.set(candidate.id, candidate);
2384
+ }
2385
+ if (selected.size >= maxPool) break;
2386
+ }
2387
+ }
2388
+ const globalSorted = [...opportunities].sort(
2389
+ (a, b) => personaAdjustedOpportunityScore(b, profile) - personaAdjustedOpportunityScore(a, profile)
2390
+ );
2391
+ for (const candidate of globalSorted) {
2392
+ if (selected.size >= maxPool) break;
2393
+ selected.set(candidate.id, candidate);
2394
+ }
2395
+ return [...selected.values()].slice(0, maxPool);
2396
+ }
2100
2397
  function cleanRewriteOutput(text) {
2101
2398
  const cleaned = text.replace(/```[\s\S]*?```/g, "").replace(/^["'`]+|["'`]+$/g, "").trim();
2102
2399
  const firstLine = cleaned.split("\n").map((line) => line.trim()).filter(Boolean)[0] ?? cleaned;
@@ -2148,12 +2445,19 @@ async function rewriteDraftForHumanVoice(input) {
2148
2445
  }
2149
2446
  async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2150
2447
  const client = await getXClient();
2448
+ const identity = loadIdentity();
2449
+ const strategy = loadStrategy();
2151
2450
  const constraints = getPersonaConstraints();
2451
+ const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
2152
2452
  const strictReplyOnly = constraints.replyOnlyMode || constraints.onlyReplyToHandles.length > 0;
2453
+ const interactionActions = /* @__PURE__ */ new Set(["reply", "like", "retweet", "follow"]);
2153
2454
  const constraintLines = buildPersonaConstraintLines(constraints);
2154
2455
  if (constraintLines.length > 0) {
2155
2456
  logger.info(`Persona constraints active: ${constraintLines.join(" | ")}`);
2156
2457
  }
2458
+ logger.info(
2459
+ `Persona action profile: actions=${personaProfile.prioritizedActions.slice(0, 4).join(" > ")}, sources=${Object.entries(personaProfile.sourceBias).sort((a, b) => b[1] - a[1]).map(([source]) => source).slice(0, 3).join(" > ")}`
2460
+ );
2157
2461
  try {
2158
2462
  const delayed = await collectDelayedOutcomes(client);
2159
2463
  if (delayed.processed > 0) {
@@ -2164,7 +2468,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2164
2468
  } catch (error) {
2165
2469
  logger.warn(`Delayed outcome collection failed: ${error.message}`);
2166
2470
  }
2167
- const selfHandle = loadIdentity().handle.replace(/^@/, "").toLowerCase();
2471
+ const selfHandle = identity.handle.replace(/^@/, "").toLowerCase();
2168
2472
  const selfUserId = selfUserIdFromCredentials();
2169
2473
  const isSelfTweet = (tweet) => tweet.authorHandle.replace(/^@/, "").toLowerCase() === selfHandle || selfUserId !== null && tweet.authorId === selfUserId;
2170
2474
  const research = await runResearchPhase(client, heartbeatCount);
@@ -2193,15 +2497,39 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2193
2497
  ...filteredTopicSearchResults.flatMap((result) => result.tweets),
2194
2498
  ...filteredPeopleActivity.flatMap((person) => person.tweets)
2195
2499
  ];
2500
+ const mission = await createHeartbeatMission({
2501
+ ...research,
2502
+ timeline: filteredTimeline,
2503
+ mentions: filteredMentions,
2504
+ topicSearchResults: filteredTopicSearchResults,
2505
+ peopleActivity: filteredPeopleActivity
2506
+ });
2507
+ logger.info(
2508
+ `Mission: mode=${mission.mode}, objective="${mission.objective}", allowed=${mission.allowedActions.join(",")}, targets=${mission.targetHandles.length > 0 ? mission.targetHandles.map((h) => `@${h}`).join(",") : "none"}`
2509
+ );
2196
2510
  logger.info(
2197
2511
  `Autonomy context: timeline=${filteredTimeline.length}, mentions=${filteredMentions.length}, topicTweets=${filteredTopicSearchResults.reduce((sum, r) => sum + r.tweets.length, 0)}, peopleTweets=${filteredPeopleActivity.reduce((sum, p) => sum + p.tweets.length, 0)}`
2198
2512
  );
2199
2513
  const systemPrompt = buildSystemPrompt();
2200
2514
  const actions = [];
2201
2515
  const results = [];
2202
- const policyFeedback = [];
2516
+ const policyFeedback = [
2517
+ `Heartbeat mission: ${mission.objective}`,
2518
+ `Mission mode: ${mission.mode}`
2519
+ ];
2203
2520
  const blockedTweetIds = new Set(recentReplyTargetIds);
2204
- const disallowedActions = /* @__PURE__ */ new Set();
2521
+ const allowedActionSet = new Set(mission.allowedActions);
2522
+ const disallowedActions = new Set(
2523
+ ["reply", "like", "retweet", "follow", "post"].filter((action) => !allowedActionSet.has(action))
2524
+ );
2525
+ if (mission.targetHandles.length > 0) {
2526
+ policyFeedback.push(`Mission targets: ${mission.targetHandles.map((handle) => `@${handle}`).join(", ")}`);
2527
+ }
2528
+ if (mission.focusTopics.length > 0) {
2529
+ policyFeedback.push(`Mission focus: ${mission.focusTopics.slice(0, 5).join(", ")}`);
2530
+ }
2531
+ policyFeedback.push(`Mission rationale: ${mission.rationale}`);
2532
+ const missionTargetSet = new Set(mission.targetHandles.map((handle) => normalizeHandle5(handle)).filter(Boolean));
2205
2533
  let replyRejectionCount = 0;
2206
2534
  const opportunities = buildActionOpportunities({
2207
2535
  research: {
@@ -2215,27 +2543,28 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2215
2543
  selfUserId,
2216
2544
  maxCandidates: 30
2217
2545
  });
2218
- const planningPool = selectBanditOpportunityPool(opportunities, 24);
2546
+ const personaSeedPool = buildPersonaGeneratedPool(opportunities, 30, personaProfile);
2547
+ const banditPool = selectBanditOpportunityPool(personaSeedPool, 24);
2548
+ const planningPool = buildPersonaGeneratedPool(banditPool, 24, personaProfile);
2219
2549
  const byType = planningPool.reduce((acc, opportunity) => {
2220
2550
  acc[opportunity.actionType] = (acc[opportunity.actionType] ?? 0) + 1;
2221
2551
  return acc;
2222
2552
  }, {});
2223
2553
  logger.info(`Opportunities selected: total=${planningPool.length}, byType=${JSON.stringify(byType)}`);
2224
- const strategy = loadStrategy();
2225
2554
  const activeIntents = listIntents();
2226
2555
  const intentTargetHandles = new Set(
2227
- activeIntents.flatMap((intent) => intent.targetHandles.map((handle) => normalizeHandle4(handle)))
2556
+ activeIntents.flatMap((intent) => intent.targetHandles.map((handle) => normalizeHandle5(handle)))
2228
2557
  );
2229
2558
  const priorityHandles = /* @__PURE__ */ new Set(
2230
2559
  [
2231
- ...strategy.peopleToEngage.filter((person) => person.priority === "high").map((person) => normalizeHandle4(person.handle)).filter(Boolean),
2560
+ ...strategy.peopleToEngage.filter((person) => person.priority === "high").map((person) => normalizeHandle5(person.handle)).filter(Boolean),
2232
2561
  ...intentTargetHandles
2233
2562
  ]
2234
2563
  );
2235
2564
  const authorByTweetId = /* @__PURE__ */ new Map();
2236
2565
  for (const opportunity of planningPool) {
2237
2566
  if (opportunity.tweetId && opportunity.authorHandle) {
2238
- authorByTweetId.set(opportunity.tweetId, normalizeHandle4(opportunity.authorHandle));
2567
+ authorByTweetId.set(opportunity.tweetId, normalizeHandle5(opportunity.authorHandle));
2239
2568
  }
2240
2569
  }
2241
2570
  getRecentInteractions(20);
@@ -2243,6 +2572,10 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2243
2572
  return planningPool.filter((opportunity) => {
2244
2573
  if (disallowedActions.has(opportunity.actionType)) return false;
2245
2574
  if (opportunity.tweetId && blockedTweetIds.has(opportunity.tweetId)) return false;
2575
+ if (missionTargetSet.size > 0 && interactionActions.has(opportunity.actionType)) {
2576
+ const author = normalizeHandle5(opportunity.authorHandle);
2577
+ if (!author || !missionTargetSet.has(author)) return false;
2578
+ }
2246
2579
  return true;
2247
2580
  });
2248
2581
  };
@@ -2274,18 +2607,18 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2274
2607
  const authorByTweetId2 = /* @__PURE__ */ new Map();
2275
2608
  for (const opportunity of candidates) {
2276
2609
  if (opportunity.tweetId && opportunity.authorHandle) {
2277
- authorByTweetId2.set(opportunity.tweetId, normalizeHandle4(opportunity.authorHandle));
2610
+ authorByTweetId2.set(opportunity.tweetId, normalizeHandle5(opportunity.authorHandle));
2278
2611
  }
2279
2612
  }
2280
2613
  const touchesPriorityTarget = plannedActions.some((action) => {
2281
- if (action.handle && priorityHandles.has(normalizeHandle4(action.handle))) return true;
2282
- if (action.targetHandle && priorityHandles.has(normalizeHandle4(action.targetHandle))) return true;
2614
+ if (action.handle && priorityHandles.has(normalizeHandle5(action.handle))) return true;
2615
+ if (action.targetHandle && priorityHandles.has(normalizeHandle5(action.targetHandle))) return true;
2283
2616
  if (action.tweetId && priorityHandles.has(authorByTweetId2.get(action.tweetId) ?? "")) return true;
2284
2617
  return false;
2285
2618
  });
2286
2619
  if (!touchesPriorityTarget) {
2287
2620
  const priorityCandidate = candidates.find(
2288
- (opportunity) => Boolean(opportunity.authorHandle) && priorityHandles.has(normalizeHandle4(opportunity.authorHandle)) && (!strictReplyOnly ? !opportunity.requiresContent : opportunity.actionType === "reply")
2621
+ (opportunity) => Boolean(opportunity.authorHandle) && priorityHandles.has(normalizeHandle5(opportunity.authorHandle)) && (!strictReplyOnly ? !opportunity.requiresContent : opportunity.actionType === "reply")
2289
2622
  );
2290
2623
  if (priorityCandidate) {
2291
2624
  plannedActions = [
@@ -2377,12 +2710,12 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2377
2710
  actions.push(candidateAction);
2378
2711
  results.push(result);
2379
2712
  if (activeIntents.length > 0) {
2380
- const touchedHandle = normalizeHandle4(
2713
+ const touchedHandle = normalizeHandle5(
2381
2714
  candidateAction.targetHandle ?? candidateAction.handle ?? (candidateAction.tweetId ? authorByTweetId.get(candidateAction.tweetId) : void 0)
2382
2715
  );
2383
2716
  const loweredContent = (candidateAction.content ?? "").toLowerCase();
2384
2717
  for (const intent of activeIntents) {
2385
- const handleMatch = touchedHandle.length > 0 && intent.targetHandles.some((handle) => normalizeHandle4(handle) === touchedHandle);
2718
+ const handleMatch = touchedHandle.length > 0 && intent.targetHandles.some((handle) => normalizeHandle5(handle) === touchedHandle);
2386
2719
  const topicMatch = loweredContent.length > 0 && intent.focusTopics.some((topic) => loweredContent.includes(topic.toLowerCase()));
2387
2720
  if (!handleMatch && !topicMatch) continue;
2388
2721
  recordIntentExecution(intent.id, {
@@ -2433,4 +2766,4 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
2433
2766
  export {
2434
2767
  runAutonomyCycle
2435
2768
  };
2436
- //# sourceMappingURL=chunk-FBHLDOMC.js.map
2769
+ //# sourceMappingURL=chunk-TTM54LQR.js.map