spora 0.7.5 → 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.
- package/dist/{autonomy-DFPA3OK6.js → autonomy-NNFTM5NW.js} +3 -3
- package/dist/{chunk-A2XTKC7B.js → chunk-JIMONWKO.js} +470 -1
- package/dist/chunk-JIMONWKO.js.map +1 -0
- package/dist/{chunk-HXI2EH5C.js → chunk-TTM54LQR.js} +487 -56
- package/dist/chunk-TTM54LQR.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/{heartbeat-RAOEIKWC.js → heartbeat-WJJSGUAQ.js} +3 -3
- package/dist/{init-ETUTFHT6.js → init-6HY4ZPFJ.js} +52 -5
- package/dist/init-6HY4ZPFJ.js.map +1 -0
- package/dist/{prompt-builder-XJHXZCSQ.js → prompt-builder-ZFUZNQY2.js} +2 -2
- package/dist/{web-chat-TB3ACPVF.js → web-chat-AKUEBSWS.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-A2XTKC7B.js.map +0 -1
- package/dist/chunk-HXI2EH5C.js.map +0 -1
- package/dist/init-ETUTFHT6.js.map +0 -1
- /package/dist/{autonomy-DFPA3OK6.js.map → autonomy-NNFTM5NW.js.map} +0 -0
- /package/dist/{heartbeat-RAOEIKWC.js.map → heartbeat-WJJSGUAQ.js.map} +0 -0
- /package/dist/{prompt-builder-XJHXZCSQ.js.map → prompt-builder-ZFUZNQY2.js.map} +0 -0
- /package/dist/{web-chat-TB3ACPVF.js.map → web-chat-AKUEBSWS.js.map} +0 -0
|
@@ -6,8 +6,12 @@ import {
|
|
|
6
6
|
} from "./chunk-ZLSDFYBR.js";
|
|
7
7
|
import {
|
|
8
8
|
buildOpportunityPortfolioMessage,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
buildPersonaConstraintLines,
|
|
10
|
+
buildSystemPrompt,
|
|
11
|
+
compilePersonaActionProfile,
|
|
12
|
+
getPersonaConstraints,
|
|
13
|
+
personaConstraintHandles
|
|
14
|
+
} from "./chunk-JIMONWKO.js";
|
|
11
15
|
import {
|
|
12
16
|
listIntents,
|
|
13
17
|
loadStrategy,
|
|
@@ -510,6 +514,12 @@ function isWritingAction(action) {
|
|
|
510
514
|
function normalizeHandle(handle) {
|
|
511
515
|
return (handle ?? "").replace(/^@/, "").trim().toLowerCase();
|
|
512
516
|
}
|
|
517
|
+
function resolveActionTargetHandle(action, tweetById) {
|
|
518
|
+
if (action.handle) return normalizeHandle(action.handle);
|
|
519
|
+
if (action.targetHandle) return normalizeHandle(action.targetHandle);
|
|
520
|
+
if (action.tweetId) return normalizeHandle(tweetById.get(action.tweetId)?.authorHandle);
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
513
523
|
function executedWrittenContent(executedActions) {
|
|
514
524
|
return executedActions.filter((a) => isWritingAction(a) && typeof a.content === "string").map((a) => a.content?.trim() ?? "").filter((content) => content.length > 0);
|
|
515
525
|
}
|
|
@@ -583,6 +593,44 @@ function evaluateActionPolicy(context) {
|
|
|
583
593
|
const selfId = (selfUserId ?? "").trim();
|
|
584
594
|
const knownTweets = observedTweets ?? [...timeline, ...mentions];
|
|
585
595
|
const tweetById = new Map(knownTweets.map((tweet) => [tweet.id, tweet]));
|
|
596
|
+
const constraints = getPersonaConstraints();
|
|
597
|
+
const strictReplyHandles = new Set(constraints.onlyReplyToHandles.map((handle) => normalizeHandle(handle)));
|
|
598
|
+
const strictInteractHandles = new Set(personaConstraintHandles(constraints).map((handle) => normalizeHandle(handle)));
|
|
599
|
+
const targetHandle = resolveActionTargetHandle(action, tweetById);
|
|
600
|
+
if (constraints.replyOnlyMode && !["reply", "skip", "learn", "reflect"].includes(action.action)) {
|
|
601
|
+
return {
|
|
602
|
+
allowed: false,
|
|
603
|
+
reason: "Persona is in reply-only mode. Use reply actions only."
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (constraints.noOriginalPosts && (action.action === "post" || action.action === "schedule")) {
|
|
607
|
+
return {
|
|
608
|
+
allowed: false,
|
|
609
|
+
reason: "Persona forbids standalone original posts."
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (strictReplyHandles.size > 0) {
|
|
613
|
+
if (action.action !== "reply") {
|
|
614
|
+
return {
|
|
615
|
+
allowed: false,
|
|
616
|
+
reason: `Persona requires replying only to specific target(s): @${[...strictReplyHandles].join(", @")}.`
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (!targetHandle || !strictReplyHandles.has(targetHandle)) {
|
|
620
|
+
return {
|
|
621
|
+
allowed: false,
|
|
622
|
+
reason: `Reply target must be one of @${[...strictReplyHandles].join(", @")}.`
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (strictInteractHandles.size > 0 && ["reply", "like", "retweet", "follow"].includes(action.action)) {
|
|
627
|
+
if (!targetHandle || !strictInteractHandles.has(targetHandle)) {
|
|
628
|
+
return {
|
|
629
|
+
allowed: false,
|
|
630
|
+
reason: `Interaction target must be one of @${[...strictInteractHandles].join(", @")}.`
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
586
634
|
if (isDuplicateTarget(action, executedActions)) {
|
|
587
635
|
return { allowed: false, reason: `Action ${action.action} already executed for tweet ${action.tweetId} this heartbeat.` };
|
|
588
636
|
}
|
|
@@ -736,8 +784,9 @@ function evaluateActionPolicy(context) {
|
|
|
736
784
|
}
|
|
737
785
|
}
|
|
738
786
|
if (action.action === "reply" && action.tweetId) {
|
|
787
|
+
const inStrictTargetMode = strictReplyHandles.size > 0 || strictInteractHandles.size > 0;
|
|
739
788
|
const repliesThisHeartbeat = executedActions.filter((a) => a.action === "reply").length;
|
|
740
|
-
if (repliesThisHeartbeat >= 2) {
|
|
789
|
+
if (repliesThisHeartbeat >= 2 && !inStrictTargetMode) {
|
|
741
790
|
return {
|
|
742
791
|
allowed: false,
|
|
743
792
|
reason: "Reply cap reached for this heartbeat. Use another tool action."
|
|
@@ -748,7 +797,7 @@ function evaluateActionPolicy(context) {
|
|
|
748
797
|
const repliedAuthors = new Set(
|
|
749
798
|
executedActions.filter((a) => a.action === "reply" && a.tweetId).map((a) => normalizeHandle(tweetById.get(a.tweetId)?.authorHandle)).filter(Boolean)
|
|
750
799
|
);
|
|
751
|
-
if (repliedAuthors.has(currentAuthor)) {
|
|
800
|
+
if (repliedAuthors.has(currentAuthor) && !inStrictTargetMode) {
|
|
752
801
|
return {
|
|
753
802
|
allowed: false,
|
|
754
803
|
reason: `Already replied to @${currentAuthor} this heartbeat. Diversify interactions.`
|
|
@@ -973,6 +1022,9 @@ function shuffle(items) {
|
|
|
973
1022
|
}
|
|
974
1023
|
return arr;
|
|
975
1024
|
}
|
|
1025
|
+
function clampInt(value, min, max) {
|
|
1026
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
1027
|
+
}
|
|
976
1028
|
function buildTopicQueryVariants(topic) {
|
|
977
1029
|
const trimmed = topic.trim();
|
|
978
1030
|
if (!trimmed) return [];
|
|
@@ -1194,21 +1246,38 @@ async function runResearchPhase(client, heartbeatCount) {
|
|
|
1194
1246
|
async function runTopicSearch(client, heartbeatCount, seedTweets) {
|
|
1195
1247
|
try {
|
|
1196
1248
|
const identity = loadIdentity();
|
|
1249
|
+
const constraints = getPersonaConstraints(identity);
|
|
1250
|
+
const strictHandles = personaConstraintHandles(constraints).filter(Boolean);
|
|
1197
1251
|
const strategy = loadStrategy();
|
|
1252
|
+
const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
|
|
1198
1253
|
const selfHandle = canonicalAgentHandle(identity.handle);
|
|
1254
|
+
const topicSearchBias = personaProfile.sourceBias.topic_search;
|
|
1255
|
+
const peopleWatchBias = personaProfile.sourceBias.people_watch;
|
|
1199
1256
|
const allTopics = [
|
|
1257
|
+
...personaProfile.priorityTopics,
|
|
1200
1258
|
...identity.topics ?? [],
|
|
1201
1259
|
...strategy.currentFocus
|
|
1202
1260
|
].flatMap(splitTopicSignal).filter(Boolean);
|
|
1203
|
-
const
|
|
1261
|
+
const discoveredBudget = clampInt(1 + Math.max(0, topicSearchBias), 1, 3);
|
|
1262
|
+
const discovered = discoverQueriesFromTweets(seedTweets, discoveredBudget);
|
|
1204
1263
|
const targetHandles = [.../* @__PURE__ */ new Set([
|
|
1205
1264
|
...strategy.peopleToEngage.filter((person) => person.priority === "high" || person.priority === "medium").map((person) => person.handle.replace(/^@/, "").toLowerCase()),
|
|
1206
1265
|
...(identity.heroes ?? []).map((handle) => handle.replace(/^@/, "").toLowerCase())
|
|
1207
1266
|
])].filter((handle) => handle && handle !== selfHandle);
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1267
|
+
const strictQueries = buildTargetedPersonQueries(
|
|
1268
|
+
strictHandles.filter((handle) => handle !== selfHandle),
|
|
1269
|
+
heartbeatCount,
|
|
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)
|
|
1276
|
+
);
|
|
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)) : [];
|
|
1280
|
+
const topicsToSearch = strictQueries.length > 0 ? [...new Set(strictQueries)] : [.../* @__PURE__ */ new Set([...topicQueries, ...discoveredQueries, ...personQueries])];
|
|
1212
1281
|
if (topicsToSearch.length === 0) return [];
|
|
1213
1282
|
const results = [];
|
|
1214
1283
|
for (const topicQuery of topicsToSearch) {
|
|
@@ -1232,10 +1301,35 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
|
|
|
1232
1301
|
try {
|
|
1233
1302
|
const strategy = loadStrategy();
|
|
1234
1303
|
const identity = loadIdentity();
|
|
1304
|
+
const constraints = getPersonaConstraints(identity);
|
|
1305
|
+
const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
|
|
1306
|
+
const strictHandles = personaConstraintHandles(constraints).map((handle) => canonicalAgentHandle(handle)).filter((handle) => handle.length > 0);
|
|
1235
1307
|
const selfHandle = identity.handle.replace(/^@/, "").toLowerCase();
|
|
1236
1308
|
const relationships = loadRelationships();
|
|
1237
1309
|
const networkHandles = listAgentNetworkHandles(20);
|
|
1238
1310
|
const networkHandleSet = new Set(networkHandles);
|
|
1311
|
+
if (strictHandles.length > 0) {
|
|
1312
|
+
const strictResults = [];
|
|
1313
|
+
for (const handle of strictHandles) {
|
|
1314
|
+
if (handle === selfHandle) continue;
|
|
1315
|
+
const userId = await resolveHandleToId(client, handle);
|
|
1316
|
+
if (!userId) continue;
|
|
1317
|
+
try {
|
|
1318
|
+
const tweets = await client.getUserTweets(userId, { count: 8 });
|
|
1319
|
+
if (tweets.length === 0) continue;
|
|
1320
|
+
strictResults.push({
|
|
1321
|
+
handle,
|
|
1322
|
+
userId,
|
|
1323
|
+
reason: "persona hard constraint",
|
|
1324
|
+
tweets
|
|
1325
|
+
});
|
|
1326
|
+
upsertAgentHandle(handle, "observed", "people check (strict persona target)");
|
|
1327
|
+
logger.info(`People check @${handle}: ${tweets.length} recent tweets (strict target mode)`);
|
|
1328
|
+
} catch {
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return strictResults;
|
|
1332
|
+
}
|
|
1239
1333
|
const people = [];
|
|
1240
1334
|
const seen = /* @__PURE__ */ new Set();
|
|
1241
1335
|
const highPriorityHandles = new Set(
|
|
@@ -1257,9 +1351,10 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
|
|
|
1257
1351
|
for (const p of strategy.peopleToEngage.filter((p2) => p2.priority === "high")) {
|
|
1258
1352
|
addPerson(p.handle, p.reason, true);
|
|
1259
1353
|
}
|
|
1354
|
+
const sporaPinInterval = personaProfile.sourceBias.people_watch >= 0.6 ? 4 : 8;
|
|
1260
1355
|
for (const handle of networkHandles) {
|
|
1261
1356
|
if (handle === "sporaai") {
|
|
1262
|
-
const pinSpora = heartbeatCount %
|
|
1357
|
+
const pinSpora = heartbeatCount % sporaPinInterval === 0;
|
|
1263
1358
|
addPerson(handle, "core Spora profile", pinSpora);
|
|
1264
1359
|
} else {
|
|
1265
1360
|
addPerson(handle, "spora agent network");
|
|
@@ -1284,7 +1379,8 @@ async function runPeopleMonitoring(client, heartbeatCount, seedTweets) {
|
|
|
1284
1379
|
addPerson(handle, "active voice in current conversations");
|
|
1285
1380
|
}
|
|
1286
1381
|
if (people.length === 0) return [];
|
|
1287
|
-
const
|
|
1382
|
+
const monitoringBudget = clampInt(3 + personaProfile.sourceBias.people_watch, 2, 5);
|
|
1383
|
+
const budget = Math.min(monitoringBudget, people.length);
|
|
1288
1384
|
const selected = [];
|
|
1289
1385
|
const selectedHandles = /* @__PURE__ */ new Set();
|
|
1290
1386
|
const pinned = people.filter((person) => person.pinned);
|
|
@@ -1384,7 +1480,9 @@ function unique(values) {
|
|
|
1384
1480
|
}
|
|
1385
1481
|
function buildPersonaContext() {
|
|
1386
1482
|
const identity = loadIdentity();
|
|
1483
|
+
const constraints = getPersonaConstraints(identity);
|
|
1387
1484
|
const strategy = loadStrategy();
|
|
1485
|
+
const actionProfile = compilePersonaActionProfile({ identity, strategy, constraints });
|
|
1388
1486
|
const focusKeywords = unique(
|
|
1389
1487
|
[
|
|
1390
1488
|
...identity.topics,
|
|
@@ -1400,11 +1498,29 @@ function buildPersonaContext() {
|
|
|
1400
1498
|
const priorityHandles = new Set(
|
|
1401
1499
|
strategy.peopleToEngage.filter((person) => person.priority === "high").map((person) => normalizeHandle3(person.handle)).filter(Boolean)
|
|
1402
1500
|
);
|
|
1501
|
+
for (const handle of personaConstraintHandles(constraints)) {
|
|
1502
|
+
priorityHandles.add(normalizeHandle3(handle));
|
|
1503
|
+
}
|
|
1403
1504
|
const heroHandles = new Set(
|
|
1404
1505
|
(identity.heroes ?? []).map((handle) => normalizeHandle3(handle)).filter(Boolean)
|
|
1405
1506
|
);
|
|
1406
1507
|
const networkHandles = new Set(listAgentNetworkHandles(60).map((handle) => normalizeHandle3(handle)).filter(Boolean));
|
|
1407
|
-
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;
|
|
1408
1524
|
}
|
|
1409
1525
|
function phraseMatchScore(text, keywords) {
|
|
1410
1526
|
if (keywords.length === 0) return 0;
|
|
@@ -1505,7 +1621,11 @@ function buildActionOpportunities(input) {
|
|
|
1505
1621
|
const maxCandidates = input.maxCandidates ?? 28;
|
|
1506
1622
|
const selfHandle = normalizeHandle3(input.selfHandle);
|
|
1507
1623
|
const selfUserId = (input.selfUserId ?? "").trim();
|
|
1624
|
+
const constraints = getPersonaConstraints();
|
|
1625
|
+
const onlyReplyHandles = new Set(constraints.onlyReplyToHandles.map((h) => normalizeHandle3(h)));
|
|
1626
|
+
const onlyInteractHandles = new Set(constraints.onlyInteractWithHandles.map((h) => normalizeHandle3(h)));
|
|
1508
1627
|
const persona = buildPersonaContext();
|
|
1628
|
+
const actionProfile = persona.actionProfile;
|
|
1509
1629
|
const recent = getRecentInteractions(300);
|
|
1510
1630
|
const {
|
|
1511
1631
|
repliedTweetIds,
|
|
@@ -1529,6 +1649,7 @@ function buildActionOpportunities(input) {
|
|
|
1529
1649
|
const handle = normalizeHandle3(candidate.tweet.authorHandle);
|
|
1530
1650
|
if (!candidate.tweet.id || !handle || handle === selfHandle) continue;
|
|
1531
1651
|
if (selfUserId && candidate.tweet.authorId === selfUserId) continue;
|
|
1652
|
+
if (onlyInteractHandles.size > 0 && !onlyInteractHandles.has(handle)) continue;
|
|
1532
1653
|
const existing = dedupedByTweetId.get(candidate.tweet.id);
|
|
1533
1654
|
if (!existing || sourceWeight(candidate.source) > sourceWeight(existing.source)) {
|
|
1534
1655
|
dedupedByTweetId.set(candidate.tweet.id, candidate);
|
|
@@ -1552,27 +1673,33 @@ function buildActionOpportunities(input) {
|
|
|
1552
1673
|
persona
|
|
1553
1674
|
);
|
|
1554
1675
|
const shortTweet = clip(tweet.text, 170);
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
+
}
|
|
1568
1695
|
const stronglyRelevant = alignment >= 0.35 || source === "mention" || source === "people_watch" || persona.priorityHandles.has(handle);
|
|
1569
|
-
if (!likedTweetIds.has(tweet.id) &&
|
|
1696
|
+
if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !isActionStronglySuppressed("like", actionProfile) && matchesPersonaGeneratedProfile("like", source, actionProfile) && !likedTweetIds.has(tweet.id) && likeScore >= 1.8 && stronglyRelevant) {
|
|
1570
1697
|
opportunities.push({
|
|
1571
1698
|
id: nextId(),
|
|
1572
1699
|
armKey: armKey("like", source),
|
|
1573
1700
|
actionType: "like",
|
|
1574
1701
|
source,
|
|
1575
|
-
score:
|
|
1702
|
+
score: likeScore,
|
|
1576
1703
|
summary: `Like @${handle} from ${source}`,
|
|
1577
1704
|
authorHandle: handle,
|
|
1578
1705
|
tweetId: tweet.id,
|
|
@@ -1581,13 +1708,13 @@ function buildActionOpportunities(input) {
|
|
|
1581
1708
|
context: `@${handle}: "${shortTweet}"`
|
|
1582
1709
|
});
|
|
1583
1710
|
}
|
|
1584
|
-
if (!retweetedTweetIds.has(tweet.id) &&
|
|
1711
|
+
if (onlyReplyHandles.size === 0 && !constraints.replyOnlyMode && !isActionStronglySuppressed("retweet", actionProfile) && matchesPersonaGeneratedProfile("retweet", source, actionProfile) && !retweetedTweetIds.has(tweet.id) && retweetScore >= 2.6 && alignment >= 0.15) {
|
|
1585
1712
|
opportunities.push({
|
|
1586
1713
|
id: nextId(),
|
|
1587
1714
|
armKey: armKey("retweet", source),
|
|
1588
1715
|
actionType: "retweet",
|
|
1589
1716
|
source,
|
|
1590
|
-
score:
|
|
1717
|
+
score: retweetScore,
|
|
1591
1718
|
summary: `Retweet @${handle} from ${source}`,
|
|
1592
1719
|
authorHandle: handle,
|
|
1593
1720
|
tweetId: tweet.id,
|
|
@@ -1596,7 +1723,7 @@ function buildActionOpportunities(input) {
|
|
|
1596
1723
|
context: `@${handle}: "${shortTweet}"`
|
|
1597
1724
|
});
|
|
1598
1725
|
}
|
|
1599
|
-
const shouldConsiderFollow = !followedHandles.has(handle) && !followOpportunityByHandle.has(handle) && !isLikelySyntheticHandle2(handle) &&
|
|
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);
|
|
1600
1727
|
if (shouldConsiderFollow) {
|
|
1601
1728
|
followOpportunityByHandle.add(handle);
|
|
1602
1729
|
opportunities.push({
|
|
@@ -1604,7 +1731,7 @@ function buildActionOpportunities(input) {
|
|
|
1604
1731
|
armKey: armKey("follow", source),
|
|
1605
1732
|
actionType: "follow",
|
|
1606
1733
|
source,
|
|
1607
|
-
score:
|
|
1734
|
+
score: followScore,
|
|
1608
1735
|
summary: `Follow @${handle}`,
|
|
1609
1736
|
authorHandle: handle,
|
|
1610
1737
|
requiresContent: false,
|
|
@@ -1614,17 +1741,19 @@ function buildActionOpportunities(input) {
|
|
|
1614
1741
|
}
|
|
1615
1742
|
}
|
|
1616
1743
|
const queryList = research.topicSearchResults.map((r) => r.query).filter(Boolean);
|
|
1617
|
-
if (queryList.length > 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(", ");
|
|
1618
1747
|
opportunities.push({
|
|
1619
1748
|
id: nextId(),
|
|
1620
1749
|
armKey: armKey("post", "synthesis"),
|
|
1621
1750
|
actionType: "post",
|
|
1622
1751
|
source: "synthesis",
|
|
1623
|
-
score:
|
|
1752
|
+
score: synthesisScore,
|
|
1624
1753
|
summary: "Post an original thought synthesized from active conversations",
|
|
1625
1754
|
requiresContent: true,
|
|
1626
1755
|
template: { action: "post", source: "synthesis" },
|
|
1627
|
-
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}` : ""}`
|
|
1628
1757
|
});
|
|
1629
1758
|
}
|
|
1630
1759
|
opportunities.sort((a, b) => b.score - a.score);
|
|
@@ -1697,6 +1826,202 @@ async function planActionPortfolio(input) {
|
|
|
1697
1826
|
return actions;
|
|
1698
1827
|
}
|
|
1699
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
|
+
|
|
1700
2025
|
// src/runtime/bandit.ts
|
|
1701
2026
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1702
2027
|
var DEFAULT_EXPLORATION_BUDGET = 0.25;
|
|
@@ -1941,7 +2266,7 @@ function selfUserIdFromCredentials() {
|
|
|
1941
2266
|
return null;
|
|
1942
2267
|
}
|
|
1943
2268
|
}
|
|
1944
|
-
function
|
|
2269
|
+
function normalizeHandle5(handle) {
|
|
1945
2270
|
return (handle ?? "").replace(/^@/, "").trim().toLowerCase();
|
|
1946
2271
|
}
|
|
1947
2272
|
function roughWordCount(text) {
|
|
@@ -2011,6 +2336,64 @@ function shouldAttemptStyleRewrite(action, reason) {
|
|
|
2011
2336
|
const lower = reason.toLowerCase();
|
|
2012
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");
|
|
2013
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
|
+
}
|
|
2014
2397
|
function cleanRewriteOutput(text) {
|
|
2015
2398
|
const cleaned = text.replace(/```[\s\S]*?```/g, "").replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
2016
2399
|
const firstLine = cleaned.split("\n").map((line) => line.trim()).filter(Boolean)[0] ?? cleaned;
|
|
@@ -2062,6 +2445,19 @@ async function rewriteDraftForHumanVoice(input) {
|
|
|
2062
2445
|
}
|
|
2063
2446
|
async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
2064
2447
|
const client = await getXClient();
|
|
2448
|
+
const identity = loadIdentity();
|
|
2449
|
+
const strategy = loadStrategy();
|
|
2450
|
+
const constraints = getPersonaConstraints();
|
|
2451
|
+
const personaProfile = compilePersonaActionProfile({ identity, strategy, constraints });
|
|
2452
|
+
const strictReplyOnly = constraints.replyOnlyMode || constraints.onlyReplyToHandles.length > 0;
|
|
2453
|
+
const interactionActions = /* @__PURE__ */ new Set(["reply", "like", "retweet", "follow"]);
|
|
2454
|
+
const constraintLines = buildPersonaConstraintLines(constraints);
|
|
2455
|
+
if (constraintLines.length > 0) {
|
|
2456
|
+
logger.info(`Persona constraints active: ${constraintLines.join(" | ")}`);
|
|
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
|
+
);
|
|
2065
2461
|
try {
|
|
2066
2462
|
const delayed = await collectDelayedOutcomes(client);
|
|
2067
2463
|
if (delayed.processed > 0) {
|
|
@@ -2072,7 +2468,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2072
2468
|
} catch (error) {
|
|
2073
2469
|
logger.warn(`Delayed outcome collection failed: ${error.message}`);
|
|
2074
2470
|
}
|
|
2075
|
-
const selfHandle =
|
|
2471
|
+
const selfHandle = identity.handle.replace(/^@/, "").toLowerCase();
|
|
2076
2472
|
const selfUserId = selfUserIdFromCredentials();
|
|
2077
2473
|
const isSelfTweet = (tweet) => tweet.authorHandle.replace(/^@/, "").toLowerCase() === selfHandle || selfUserId !== null && tweet.authorId === selfUserId;
|
|
2078
2474
|
const research = await runResearchPhase(client, heartbeatCount);
|
|
@@ -2101,15 +2497,39 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2101
2497
|
...filteredTopicSearchResults.flatMap((result) => result.tweets),
|
|
2102
2498
|
...filteredPeopleActivity.flatMap((person) => person.tweets)
|
|
2103
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
|
+
);
|
|
2104
2510
|
logger.info(
|
|
2105
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)}`
|
|
2106
2512
|
);
|
|
2107
2513
|
const systemPrompt = buildSystemPrompt();
|
|
2108
2514
|
const actions = [];
|
|
2109
2515
|
const results = [];
|
|
2110
|
-
const policyFeedback = [
|
|
2516
|
+
const policyFeedback = [
|
|
2517
|
+
`Heartbeat mission: ${mission.objective}`,
|
|
2518
|
+
`Mission mode: ${mission.mode}`
|
|
2519
|
+
];
|
|
2111
2520
|
const blockedTweetIds = new Set(recentReplyTargetIds);
|
|
2112
|
-
const
|
|
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));
|
|
2113
2533
|
let replyRejectionCount = 0;
|
|
2114
2534
|
const opportunities = buildActionOpportunities({
|
|
2115
2535
|
research: {
|
|
@@ -2123,27 +2543,28 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2123
2543
|
selfUserId,
|
|
2124
2544
|
maxCandidates: 30
|
|
2125
2545
|
});
|
|
2126
|
-
const
|
|
2546
|
+
const personaSeedPool = buildPersonaGeneratedPool(opportunities, 30, personaProfile);
|
|
2547
|
+
const banditPool = selectBanditOpportunityPool(personaSeedPool, 24);
|
|
2548
|
+
const planningPool = buildPersonaGeneratedPool(banditPool, 24, personaProfile);
|
|
2127
2549
|
const byType = planningPool.reduce((acc, opportunity) => {
|
|
2128
2550
|
acc[opportunity.actionType] = (acc[opportunity.actionType] ?? 0) + 1;
|
|
2129
2551
|
return acc;
|
|
2130
2552
|
}, {});
|
|
2131
2553
|
logger.info(`Opportunities selected: total=${planningPool.length}, byType=${JSON.stringify(byType)}`);
|
|
2132
|
-
const strategy = loadStrategy();
|
|
2133
2554
|
const activeIntents = listIntents();
|
|
2134
2555
|
const intentTargetHandles = new Set(
|
|
2135
|
-
activeIntents.flatMap((intent) => intent.targetHandles.map((handle) =>
|
|
2556
|
+
activeIntents.flatMap((intent) => intent.targetHandles.map((handle) => normalizeHandle5(handle)))
|
|
2136
2557
|
);
|
|
2137
2558
|
const priorityHandles = /* @__PURE__ */ new Set(
|
|
2138
2559
|
[
|
|
2139
|
-
...strategy.peopleToEngage.filter((person) => person.priority === "high").map((person) =>
|
|
2560
|
+
...strategy.peopleToEngage.filter((person) => person.priority === "high").map((person) => normalizeHandle5(person.handle)).filter(Boolean),
|
|
2140
2561
|
...intentTargetHandles
|
|
2141
2562
|
]
|
|
2142
2563
|
);
|
|
2143
2564
|
const authorByTweetId = /* @__PURE__ */ new Map();
|
|
2144
2565
|
for (const opportunity of planningPool) {
|
|
2145
2566
|
if (opportunity.tweetId && opportunity.authorHandle) {
|
|
2146
|
-
authorByTweetId.set(opportunity.tweetId,
|
|
2567
|
+
authorByTweetId.set(opportunity.tweetId, normalizeHandle5(opportunity.authorHandle));
|
|
2147
2568
|
}
|
|
2148
2569
|
}
|
|
2149
2570
|
getRecentInteractions(20);
|
|
@@ -2151,6 +2572,10 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2151
2572
|
return planningPool.filter((opportunity) => {
|
|
2152
2573
|
if (disallowedActions.has(opportunity.actionType)) return false;
|
|
2153
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
|
+
}
|
|
2154
2579
|
return true;
|
|
2155
2580
|
});
|
|
2156
2581
|
};
|
|
@@ -2167,7 +2592,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2167
2592
|
policyFeedback,
|
|
2168
2593
|
executedActions: actions
|
|
2169
2594
|
});
|
|
2170
|
-
if (plannedActions.length > 1 && plannedActions.every((a) => a.action === "reply")) {
|
|
2595
|
+
if (!strictReplyOnly && plannedActions.length > 1 && plannedActions.every((a) => a.action === "reply")) {
|
|
2171
2596
|
const nonReplyCandidate = candidates.find(
|
|
2172
2597
|
(opportunity) => opportunity.actionType !== "reply" && !opportunity.requiresContent
|
|
2173
2598
|
);
|
|
@@ -2182,18 +2607,18 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2182
2607
|
const authorByTweetId2 = /* @__PURE__ */ new Map();
|
|
2183
2608
|
for (const opportunity of candidates) {
|
|
2184
2609
|
if (opportunity.tweetId && opportunity.authorHandle) {
|
|
2185
|
-
authorByTweetId2.set(opportunity.tweetId,
|
|
2610
|
+
authorByTweetId2.set(opportunity.tweetId, normalizeHandle5(opportunity.authorHandle));
|
|
2186
2611
|
}
|
|
2187
2612
|
}
|
|
2188
2613
|
const touchesPriorityTarget = plannedActions.some((action) => {
|
|
2189
|
-
if (action.handle && priorityHandles.has(
|
|
2190
|
-
if (action.targetHandle && priorityHandles.has(
|
|
2614
|
+
if (action.handle && priorityHandles.has(normalizeHandle5(action.handle))) return true;
|
|
2615
|
+
if (action.targetHandle && priorityHandles.has(normalizeHandle5(action.targetHandle))) return true;
|
|
2191
2616
|
if (action.tweetId && priorityHandles.has(authorByTweetId2.get(action.tweetId) ?? "")) return true;
|
|
2192
2617
|
return false;
|
|
2193
2618
|
});
|
|
2194
2619
|
if (!touchesPriorityTarget) {
|
|
2195
2620
|
const priorityCandidate = candidates.find(
|
|
2196
|
-
(opportunity) => Boolean(opportunity.authorHandle) && priorityHandles.has(
|
|
2621
|
+
(opportunity) => Boolean(opportunity.authorHandle) && priorityHandles.has(normalizeHandle5(opportunity.authorHandle)) && (!strictReplyOnly ? !opportunity.requiresContent : opportunity.actionType === "reply")
|
|
2197
2622
|
);
|
|
2198
2623
|
if (priorityCandidate) {
|
|
2199
2624
|
plannedActions = [
|
|
@@ -2271,7 +2696,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2271
2696
|
}
|
|
2272
2697
|
if (candidateAction.action === "reply") {
|
|
2273
2698
|
replyRejectionCount += 1;
|
|
2274
|
-
if (replyRejectionCount >= 2) {
|
|
2699
|
+
if (replyRejectionCount >= 2 && !strictReplyOnly) {
|
|
2275
2700
|
disallowedActions.add("reply");
|
|
2276
2701
|
const pivot = "Reply opportunities exhausted this heartbeat. Pivot to like/retweet/follow/post.";
|
|
2277
2702
|
policyFeedback.push(pivot);
|
|
@@ -2285,12 +2710,12 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2285
2710
|
actions.push(candidateAction);
|
|
2286
2711
|
results.push(result);
|
|
2287
2712
|
if (activeIntents.length > 0) {
|
|
2288
|
-
const touchedHandle =
|
|
2713
|
+
const touchedHandle = normalizeHandle5(
|
|
2289
2714
|
candidateAction.targetHandle ?? candidateAction.handle ?? (candidateAction.tweetId ? authorByTweetId.get(candidateAction.tweetId) : void 0)
|
|
2290
2715
|
);
|
|
2291
2716
|
const loweredContent = (candidateAction.content ?? "").toLowerCase();
|
|
2292
2717
|
for (const intent of activeIntents) {
|
|
2293
|
-
const handleMatch = touchedHandle.length > 0 && intent.targetHandles.some((handle) =>
|
|
2718
|
+
const handleMatch = touchedHandle.length > 0 && intent.targetHandles.some((handle) => normalizeHandle5(handle) === touchedHandle);
|
|
2294
2719
|
const topicMatch = loweredContent.length > 0 && intent.focusTopics.some((topic) => loweredContent.includes(topic.toLowerCase()));
|
|
2295
2720
|
if (!handleMatch && !topicMatch) continue;
|
|
2296
2721
|
recordIntentExecution(intent.id, {
|
|
@@ -2305,10 +2730,16 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2305
2730
|
blockedTweetIds.add(candidateAction.tweetId);
|
|
2306
2731
|
}
|
|
2307
2732
|
if (candidateAction.action === "reply" && /duplicate content/i.test(err)) {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2733
|
+
if (!strictReplyOnly) {
|
|
2734
|
+
disallowedActions.add("reply");
|
|
2735
|
+
const reason = "Reply failed with duplicate-content error. Switch to non-reply actions.";
|
|
2736
|
+
policyFeedback.push(reason);
|
|
2737
|
+
logger.info(`Policy adjustment: ${reason}`);
|
|
2738
|
+
} else {
|
|
2739
|
+
const reason = "Reply failed with duplicate-content error. Keep reply mode but use a new angle/target.";
|
|
2740
|
+
policyFeedback.push(reason);
|
|
2741
|
+
logger.info(`Policy adjustment: ${reason}`);
|
|
2742
|
+
}
|
|
2312
2743
|
}
|
|
2313
2744
|
if ((candidateAction.action === "post" || candidateAction.action === "schedule") && /duplicate content/i.test(err)) {
|
|
2314
2745
|
const reason = "Write-path duplicate-content failure. Change framing and try a different angle.";
|
|
@@ -2335,4 +2766,4 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
2335
2766
|
export {
|
|
2336
2767
|
runAutonomyCycle
|
|
2337
2768
|
};
|
|
2338
|
-
//# sourceMappingURL=chunk-
|
|
2769
|
+
//# sourceMappingURL=chunk-TTM54LQR.js.map
|