spora 0.7.9 → 0.7.11
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-ZMFZRXDZ.js → autonomy-ZVXD2OP2.js} +2 -2
- package/dist/{chunk-TTDQZI5W.js → chunk-3S44SIUP.js} +313 -18
- package/dist/chunk-3S44SIUP.js.map +1 -0
- package/dist/chunk-N7E4XZQN.js +99 -0
- package/dist/chunk-N7E4XZQN.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/{heartbeat-CUM7FIHS.js → heartbeat-7HPOTN5Z.js} +3 -3
- package/dist/{heartbeat-narrative-B3RD3OPJ.js → heartbeat-narrative-WGYRAWUH.js} +2 -2
- package/dist/{init-4SBU4H6F.js → init-75RMQFHC.js} +19 -5
- package/dist/init-75RMQFHC.js.map +1 -0
- package/dist/web-chat/chat.html +29 -10
- package/dist/{web-chat-YRQQB435.js → web-chat-FCOETDEZ.js} +13 -14
- package/dist/web-chat-FCOETDEZ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-OLYPPXKP.js +0 -69
- package/dist/chunk-OLYPPXKP.js.map +0 -1
- package/dist/chunk-TTDQZI5W.js.map +0 -1
- package/dist/init-4SBU4H6F.js.map +0 -1
- package/dist/web-chat-YRQQB435.js.map +0 -1
- /package/dist/{autonomy-ZMFZRXDZ.js.map → autonomy-ZVXD2OP2.js.map} +0 -0
- /package/dist/{heartbeat-CUM7FIHS.js.map → heartbeat-7HPOTN5Z.js.map} +0 -0
- /package/dist/{heartbeat-narrative-B3RD3OPJ.js.map → heartbeat-narrative-WGYRAWUH.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runAutonomyCycle
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3S44SIUP.js";
|
|
4
4
|
import "./chunk-TKGB5LIN.js";
|
|
5
5
|
import "./chunk-AIGSCHZK.js";
|
|
6
6
|
import "./chunk-73CWOI44.js";
|
|
@@ -17,4 +17,4 @@ import "./chunk-ZWKTKWS6.js";
|
|
|
17
17
|
export {
|
|
18
18
|
runAutonomyCycle
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=autonomy-
|
|
20
|
+
//# sourceMappingURL=autonomy-ZVXD2OP2.js.map
|
|
@@ -967,6 +967,37 @@ function canonicalPlannerHandle(handle) {
|
|
|
967
967
|
function canonicalizePlannerQuery(rawQuery) {
|
|
968
968
|
return rawQuery.replace(/from:([a-zA-Z0-9_]{1,15})/gi, (_match, handle) => `from:${canonicalPlannerHandle(handle)}`);
|
|
969
969
|
}
|
|
970
|
+
var REPLY_TARGET_COOLDOWN_MS = 6 * 60 * 60 * 1e3;
|
|
971
|
+
function normalizeDraftText(text) {
|
|
972
|
+
return text.toLowerCase().replace(/https?:\/\/\S+/g, "").replace(/[@#][a-z0-9_]+/g, "").replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
973
|
+
}
|
|
974
|
+
function tokenJaccard(a, b) {
|
|
975
|
+
const aTokens = new Set(normalizeDraftText(a).split(" ").filter(Boolean));
|
|
976
|
+
const bTokens = new Set(normalizeDraftText(b).split(" ").filter(Boolean));
|
|
977
|
+
if (aTokens.size === 0 || bTokens.size === 0) return 0;
|
|
978
|
+
let overlap = 0;
|
|
979
|
+
for (const token of aTokens) {
|
|
980
|
+
if (bTokens.has(token)) overlap += 1;
|
|
981
|
+
}
|
|
982
|
+
const union = aTokens.size + bTokens.size - overlap;
|
|
983
|
+
return union > 0 ? overlap / union : 0;
|
|
984
|
+
}
|
|
985
|
+
function nearDuplicateDraft(content, previous) {
|
|
986
|
+
const normalized = normalizeDraftText(content);
|
|
987
|
+
if (!normalized) return false;
|
|
988
|
+
return previous.some((sample) => {
|
|
989
|
+
const candidate = normalizeDraftText(sample);
|
|
990
|
+
if (!candidate) return false;
|
|
991
|
+
if (candidate === normalized) return true;
|
|
992
|
+
const sameOpening = normalized.split(" ").slice(0, 7).join(" ") === candidate.split(" ").slice(0, 7).join(" ");
|
|
993
|
+
if (sameOpening) return true;
|
|
994
|
+
return tokenJaccard(normalized, candidate) >= 0.86;
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
function shouldHardBlockPolicyReason(reason) {
|
|
998
|
+
const lower = reason.toLowerCase();
|
|
999
|
+
return lower.includes("already executed for tweet") || lower.includes("rejected self-interaction") || lower.includes("self-follow") || lower.includes("requires replying only") || lower.includes("reply target must be one of") || lower.includes("interaction target must be one of") || lower.includes("forbids standalone original posts") || lower.includes("reply-only mode");
|
|
1000
|
+
}
|
|
970
1001
|
function roughWordCount(text) {
|
|
971
1002
|
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
972
1003
|
}
|
|
@@ -1083,6 +1114,45 @@ async function rewriteDraftForHumanVoice(input) {
|
|
|
1083
1114
|
return null;
|
|
1084
1115
|
}
|
|
1085
1116
|
}
|
|
1117
|
+
async function composeHeartbeatPostDraft(input) {
|
|
1118
|
+
const system = [
|
|
1119
|
+
`You write one original X post for ${input.identityName} (@${input.identityHandle}).`,
|
|
1120
|
+
"Write like a real person: natural, concise, specific.",
|
|
1121
|
+
"No manifesto voice, no generic AI lecture style.",
|
|
1122
|
+
"Return ONLY the post text."
|
|
1123
|
+
].join(" ");
|
|
1124
|
+
const promptParts = [];
|
|
1125
|
+
promptParts.push(`Tone: ${input.tone || "natural"}`);
|
|
1126
|
+
promptParts.push(`Goals: ${input.goals.slice(0, 4).join(" | ") || "none"}`);
|
|
1127
|
+
promptParts.push(`Boundaries: ${input.boundaries.slice(0, 4).join(" | ") || "none"}`);
|
|
1128
|
+
promptParts.push("Constraints:");
|
|
1129
|
+
promptParts.push("- 18-180 characters");
|
|
1130
|
+
promptParts.push("- 1-2 short sentences");
|
|
1131
|
+
promptParts.push("- concrete and human sounding");
|
|
1132
|
+
promptParts.push("- don't start with @ unless absolutely needed");
|
|
1133
|
+
promptParts.push("- avoid abstract 'the real question' framing");
|
|
1134
|
+
if (input.contextTweets.length > 0) {
|
|
1135
|
+
promptParts.push("Current timeline context:");
|
|
1136
|
+
for (const tweet of input.contextTweets.slice(0, 6)) {
|
|
1137
|
+
promptParts.push(`- @${tweet.authorHandle}: ${tweet.text.slice(0, 160)}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (input.recentSamples.length > 0) {
|
|
1141
|
+
promptParts.push("Avoid repeating these recent lines:");
|
|
1142
|
+
for (const sample of input.recentSamples.slice(0, 5)) {
|
|
1143
|
+
promptParts.push(`- ${sample.slice(0, 120)}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
const response = await generateResponse(system, promptParts.join("\n"));
|
|
1148
|
+
const candidate = cleanRewriteOutput(response.content);
|
|
1149
|
+
if (!candidate) return null;
|
|
1150
|
+
if (candidate.length < 18 || candidate.length > 220) return null;
|
|
1151
|
+
return candidate;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1086
1156
|
function loopClampInt(value, min, max) {
|
|
1087
1157
|
return Math.max(min, Math.min(max, Math.round(value)));
|
|
1088
1158
|
}
|
|
@@ -1259,6 +1329,10 @@ function buildToolLoopPrompt(input) {
|
|
|
1259
1329
|
for (const line of input.constraintLines) lines.push(line);
|
|
1260
1330
|
lines.push("");
|
|
1261
1331
|
}
|
|
1332
|
+
if (input.heartbeatDirective) {
|
|
1333
|
+
lines.push(`Heartbeat directive: ${input.heartbeatDirective}`);
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
}
|
|
1262
1336
|
if (input.recentMemory.length > 0) {
|
|
1263
1337
|
lines.push("Recent memory:");
|
|
1264
1338
|
for (const row of input.recentMemory.slice(0, 8)) {
|
|
@@ -1270,6 +1344,21 @@ function buildToolLoopPrompt(input) {
|
|
|
1270
1344
|
lines.push(
|
|
1271
1345
|
`Current context counts: timeline=${input.state.timeline.length}, mentions=${input.state.mentions.length}, topicTweets=${input.state.topicSearchResults.reduce((sum, item) => sum + item.tweets.length, 0)}, peopleTweets=${input.state.peopleActivity.reduce((sum, item) => sum + item.tweets.length, 0)}`
|
|
1272
1346
|
);
|
|
1347
|
+
if (input.usedReplyTargets.length > 0) {
|
|
1348
|
+
lines.push(`Reply targets already used this heartbeat: ${input.usedReplyTargets.slice(-8).join(", ")}`);
|
|
1349
|
+
}
|
|
1350
|
+
if (input.recentReplyTargets.length > 0) {
|
|
1351
|
+
lines.push(`Recently replied targets (cooldown): ${input.recentReplyTargets.slice(0, 8).join(", ")}`);
|
|
1352
|
+
}
|
|
1353
|
+
if (input.blockedReplyTargets.length > 0) {
|
|
1354
|
+
lines.push(`Temporarily blocked reply targets this heartbeat: ${input.blockedReplyTargets.slice(0, 8).join(", ")}`);
|
|
1355
|
+
}
|
|
1356
|
+
if (input.recentWrittenSamples.length > 0) {
|
|
1357
|
+
lines.push("Recent writing to avoid repeating:");
|
|
1358
|
+
for (const sample of input.recentWrittenSamples.slice(0, 4)) {
|
|
1359
|
+
lines.push(`- ${sample.slice(0, 100)}`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1273
1362
|
lines.push("");
|
|
1274
1363
|
if (input.toolHistory.length > 0) {
|
|
1275
1364
|
lines.push("Recent tool steps:");
|
|
@@ -1316,6 +1405,8 @@ function buildToolLoopPrompt(input) {
|
|
|
1316
1405
|
lines.push("- if timeline/mentions already have viable tweets, prefer acting over more research");
|
|
1317
1406
|
lines.push("- use search/profile checks only when you have a clear reason");
|
|
1318
1407
|
lines.push("- avoid repeating the same query/target unless context changed");
|
|
1408
|
+
lines.push("- one reply per target tweet per heartbeat, always choose a new tweet next");
|
|
1409
|
+
lines.push("- keep timeline presence alive: post an original thought regularly, not only replies");
|
|
1319
1410
|
lines.push("");
|
|
1320
1411
|
lines.push("Writing constraints:");
|
|
1321
1412
|
lines.push("- sound human and specific, no manifesto language");
|
|
@@ -1374,6 +1465,10 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1374
1465
|
const client = await getXClient();
|
|
1375
1466
|
const identity = loadIdentity();
|
|
1376
1467
|
const constraints = getPersonaConstraints();
|
|
1468
|
+
const strictReplyHandles = new Set(constraints.onlyReplyToHandles.map((handle) => normalizeHandle2(handle)));
|
|
1469
|
+
const strictInteractHandles = new Set(
|
|
1470
|
+
[...constraints.onlyReplyToHandles, ...constraints.onlyInteractWithHandles].map((handle) => normalizeHandle2(handle))
|
|
1471
|
+
);
|
|
1377
1472
|
const constraintLines = buildPersonaConstraintLines(constraints);
|
|
1378
1473
|
if (constraintLines.length > 0) {
|
|
1379
1474
|
logger.info(`Persona constraints active: ${constraintLines.join(" | ")}`);
|
|
@@ -1432,9 +1527,99 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1432
1527
|
const activeIntents = listIntents();
|
|
1433
1528
|
let staleSteps = 0;
|
|
1434
1529
|
let attemptedActions = 0;
|
|
1435
|
-
const
|
|
1530
|
+
const maxConfiguredActions = Math.max(1, maxActions);
|
|
1531
|
+
let actionBudget = maxConfiguredActions;
|
|
1532
|
+
if (constraints.replyOnlyMode || strictReplyHandles.size > 0) {
|
|
1533
|
+
actionBudget = 1;
|
|
1534
|
+
} else {
|
|
1535
|
+
const upper = Math.min(maxConfiguredActions, 3);
|
|
1536
|
+
actionBudget = Math.max(1, Math.floor(Math.random() * upper) + 1);
|
|
1537
|
+
}
|
|
1538
|
+
const maxToolSteps = Math.max(8, actionBudget * 5);
|
|
1539
|
+
const recentInteractions = getRecentInteractions(220);
|
|
1540
|
+
const now = Date.now();
|
|
1541
|
+
const recentReplyTargets = new Set(
|
|
1542
|
+
recentInteractions.filter((entry) => entry.type === "reply" && typeof entry.inReplyTo === "string").filter((entry) => {
|
|
1543
|
+
const ts = Date.parse(entry.timestamp);
|
|
1544
|
+
return Number.isNaN(ts) || now - ts <= REPLY_TARGET_COOLDOWN_MS;
|
|
1545
|
+
}).map((entry) => entry.inReplyTo)
|
|
1546
|
+
);
|
|
1547
|
+
const recentWrittenSamples = recentInteractions.filter((entry) => (entry.type === "post" || entry.type === "reply") && typeof entry.content === "string").map((entry) => entry.content.trim()).filter(Boolean).slice(0, 16);
|
|
1548
|
+
const recentPostCount = recentInteractions.filter((entry) => entry.type === "post").slice(0, 30).length;
|
|
1549
|
+
const recentReplyCount = recentInteractions.filter((entry) => entry.type === "reply").slice(0, 30).length;
|
|
1550
|
+
const postStarved = recentPostCount === 0 || recentReplyCount >= recentPostCount * 3 + 2;
|
|
1551
|
+
const postFirstMode = !constraints.noOriginalPosts && !constraints.replyOnlyMode && (postStarved || heartbeatCount % 4 === 0);
|
|
1552
|
+
const minPostsThisHeartbeat = postFirstMode ? 1 : 0;
|
|
1553
|
+
if (postFirstMode) {
|
|
1554
|
+
policyFeedback.push("Heartbeat objective: publish one original post first, then engage.");
|
|
1555
|
+
}
|
|
1556
|
+
const usedReplyTargets = /* @__PURE__ */ new Set();
|
|
1557
|
+
const blockedReplyTargets = /* @__PURE__ */ new Set();
|
|
1558
|
+
const writtenThisHeartbeat = [];
|
|
1559
|
+
let blockedReplyStreak = 0;
|
|
1560
|
+
let forcePostNextStep = false;
|
|
1436
1561
|
getRecentInteractions(20);
|
|
1437
|
-
|
|
1562
|
+
const markNoProgress = (note) => {
|
|
1563
|
+
if (note) policyFeedback.push(note);
|
|
1564
|
+
staleSteps += 1;
|
|
1565
|
+
if (staleSteps >= 5) {
|
|
1566
|
+
logger.info("Tool loop stopped after repeated blocked/no-progress steps.");
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
return false;
|
|
1570
|
+
};
|
|
1571
|
+
const makeForcedPostDecision = async (reason, contextTweets) => {
|
|
1572
|
+
if (constraints.noOriginalPosts || constraints.replyOnlyMode || strictReplyHandles.size > 0) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
const forcedDraft = await composeHeartbeatPostDraft({
|
|
1576
|
+
identityName: identity.name,
|
|
1577
|
+
identityHandle: identity.handle,
|
|
1578
|
+
tone: identity.tone,
|
|
1579
|
+
goals: identity.goals ?? [],
|
|
1580
|
+
boundaries: identity.boundaries ?? [],
|
|
1581
|
+
contextTweets: contextTweets.slice(0, 8),
|
|
1582
|
+
recentSamples: [...writtenThisHeartbeat, ...recentWrittenSamples].slice(0, 8)
|
|
1583
|
+
});
|
|
1584
|
+
if (!forcedDraft) return null;
|
|
1585
|
+
return {
|
|
1586
|
+
tool: "post",
|
|
1587
|
+
args: { content: forcedDraft },
|
|
1588
|
+
reasoning: reason
|
|
1589
|
+
};
|
|
1590
|
+
};
|
|
1591
|
+
const hasEligibleStrictReplyTarget = () => {
|
|
1592
|
+
const observed = collectObserved(state);
|
|
1593
|
+
return observed.tweets.some((tweet) => {
|
|
1594
|
+
const handle = normalizeHandle2(tweet.authorHandle);
|
|
1595
|
+
return strictReplyHandles.has(handle) && !usedReplyTargets.has(tweet.id) && !recentReplyTargets.has(tweet.id) && !blockedReplyTargets.has(tweet.id);
|
|
1596
|
+
});
|
|
1597
|
+
};
|
|
1598
|
+
const onBlockedReplyDecision = (reason, tweetId) => {
|
|
1599
|
+
policyFeedback.push(reason);
|
|
1600
|
+
logger.info(`Planner loop guard: ${reason}`);
|
|
1601
|
+
if (tweetId) blockedReplyTargets.add(tweetId);
|
|
1602
|
+
blockedReplyStreak += 1;
|
|
1603
|
+
if (blockedReplyStreak >= 2) {
|
|
1604
|
+
if (!constraints.noOriginalPosts && !constraints.replyOnlyMode && strictReplyHandles.size === 0) {
|
|
1605
|
+
forcePostNextStep = true;
|
|
1606
|
+
blockedReplyStreak = 0;
|
|
1607
|
+
policyFeedback.push("Reply lane is saturated; forcing one original post next.");
|
|
1608
|
+
logger.info("Planner loop guard: reply lane saturated, forcing post pivot.");
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
policyFeedback.push("Ending loop after repeated blocked reply targets this heartbeat.");
|
|
1612
|
+
logger.info("Planner loop ended after repeated blocked reply targets.");
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1615
|
+
return false;
|
|
1616
|
+
};
|
|
1617
|
+
for (let step = 0; step < maxToolSteps && actions.length < actionBudget; step += 1) {
|
|
1618
|
+
if (strictReplyHandles.size > 0 && !hasEligibleStrictReplyTarget()) {
|
|
1619
|
+
policyFeedback.push("No eligible fresh target tweets remain for strict reply mode this heartbeat.");
|
|
1620
|
+
logger.info("Planner loop ended: no eligible strict-reply targets left.");
|
|
1621
|
+
break;
|
|
1622
|
+
}
|
|
1438
1623
|
const candidates = buildPlannerCandidates(state);
|
|
1439
1624
|
const recentMemory = getRecentInteractions(24).slice(0, 12).map((entry) => {
|
|
1440
1625
|
const text = (entry.content ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
@@ -1457,6 +1642,11 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1457
1642
|
policyFeedback,
|
|
1458
1643
|
toolHistory,
|
|
1459
1644
|
recentMemory,
|
|
1645
|
+
usedReplyTargets: [...usedReplyTargets],
|
|
1646
|
+
recentReplyTargets: [...recentReplyTargets],
|
|
1647
|
+
blockedReplyTargets: [...blockedReplyTargets],
|
|
1648
|
+
recentWrittenSamples: [...writtenThisHeartbeat, ...recentWrittenSamples].slice(0, 8),
|
|
1649
|
+
heartbeatDirective: postFirstMode && actions.length === 0 ? "Start with an original post this cycle." : void 0,
|
|
1460
1650
|
heartbeatCount
|
|
1461
1651
|
});
|
|
1462
1652
|
let decision = fallbackPlannerDecision(step, candidates.length);
|
|
@@ -1471,6 +1661,35 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1471
1661
|
} catch (error) {
|
|
1472
1662
|
policyFeedback.push(`Planner call failed: ${error.message}`);
|
|
1473
1663
|
}
|
|
1664
|
+
if (forcePostNextStep && decision.tool !== "post") {
|
|
1665
|
+
const forced = await makeForcedPostDecision("reply-lane saturation pivot", candidates);
|
|
1666
|
+
if (forced) {
|
|
1667
|
+
decision = forced;
|
|
1668
|
+
forcePostNextStep = false;
|
|
1669
|
+
policyFeedback.push("Planner adjusted: switched to an original post after reply loop saturation.");
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
if (step === 0 && actions.length === 0 && postFirstMode && decision.tool !== "post") {
|
|
1673
|
+
const forced = await makeForcedPostDecision("post-first heartbeat mode", candidates);
|
|
1674
|
+
if (forced) {
|
|
1675
|
+
decision = forced;
|
|
1676
|
+
policyFeedback.push("Planner adjusted: posting first this heartbeat to keep timeline presence.");
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
if (decision.tool !== "post") {
|
|
1680
|
+
const postsSoFar = actions.filter((a) => a.action === "post").length;
|
|
1681
|
+
const repliesSoFar = actions.filter((a) => a.action === "reply").length;
|
|
1682
|
+
const postsRemaining = Math.max(0, minPostsThisHeartbeat - postsSoFar);
|
|
1683
|
+
const remainingSlots = actionBudget - actions.length;
|
|
1684
|
+
const shouldShiftToPost = postsRemaining > 0 && (remainingSlots <= postsRemaining || postFirstMode && postsSoFar === 0 && repliesSoFar >= 1);
|
|
1685
|
+
if (shouldShiftToPost) {
|
|
1686
|
+
const forced = await makeForcedPostDecision("post-balance safeguard", candidates);
|
|
1687
|
+
if (forced) {
|
|
1688
|
+
decision = forced;
|
|
1689
|
+
policyFeedback.push("Planner adjusted: balancing heartbeat mix with an original post.");
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1474
1693
|
logger.info(`Tool loop step ${step + 1}: ${decision.tool}`);
|
|
1475
1694
|
let stepProgress = false;
|
|
1476
1695
|
if (decision.tool === "observe_timeline") {
|
|
@@ -1488,7 +1707,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1488
1707
|
} catch (error) {
|
|
1489
1708
|
policyFeedback.push(`observe_timeline failed: ${error.message}`);
|
|
1490
1709
|
}
|
|
1491
|
-
if (!stepProgress
|
|
1710
|
+
if (!stepProgress && markNoProgress("observe_timeline found no new tweets.")) break;
|
|
1492
1711
|
continue;
|
|
1493
1712
|
}
|
|
1494
1713
|
if (decision.tool === "observe_mentions") {
|
|
@@ -1506,13 +1725,13 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1506
1725
|
} catch (error) {
|
|
1507
1726
|
policyFeedback.push(`observe_mentions failed: ${error.message}`);
|
|
1508
1727
|
}
|
|
1509
|
-
if (!stepProgress
|
|
1728
|
+
if (!stepProgress && markNoProgress("observe_mentions found no new tweets.")) break;
|
|
1510
1729
|
continue;
|
|
1511
1730
|
}
|
|
1512
1731
|
if (decision.tool === "search_tweets") {
|
|
1513
1732
|
const rawQuery = argString(decision.args, "query");
|
|
1514
1733
|
if (!rawQuery) {
|
|
1515
|
-
|
|
1734
|
+
if (markNoProgress("search_tweets rejected: missing query.")) break;
|
|
1516
1735
|
continue;
|
|
1517
1736
|
}
|
|
1518
1737
|
const query = canonicalizePlannerQuery(rawQuery);
|
|
@@ -1534,14 +1753,14 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1534
1753
|
} catch (error) {
|
|
1535
1754
|
policyFeedback.push(`search_tweets failed for "${query}": ${error.message}`);
|
|
1536
1755
|
}
|
|
1537
|
-
if (!stepProgress
|
|
1756
|
+
if (!stepProgress && markNoProgress(`search_tweets "${query}" found no new tweets.`)) break;
|
|
1538
1757
|
continue;
|
|
1539
1758
|
}
|
|
1540
1759
|
if (decision.tool === "check_profile") {
|
|
1541
1760
|
const handleInput = argString(decision.args, "handle");
|
|
1542
1761
|
const handle = handleInput ? canonicalPlannerHandle(handleInput) : "";
|
|
1543
1762
|
if (!handle) {
|
|
1544
|
-
|
|
1763
|
+
if (markNoProgress("check_profile rejected: missing handle.")) break;
|
|
1545
1764
|
continue;
|
|
1546
1765
|
}
|
|
1547
1766
|
const count = argCount(decision.args, 6, 3, 12);
|
|
@@ -1565,14 +1784,13 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1565
1784
|
} catch (error) {
|
|
1566
1785
|
policyFeedback.push(`check_profile failed for @${handle}: ${error.message}`);
|
|
1567
1786
|
}
|
|
1568
|
-
if (!stepProgress
|
|
1787
|
+
if (!stepProgress && markNoProgress(`check_profile @${handle} found no new tweets.`)) break;
|
|
1569
1788
|
continue;
|
|
1570
1789
|
}
|
|
1571
1790
|
const observed = collectObserved(state);
|
|
1572
1791
|
let candidateAction = decisionToAgentAction(decision);
|
|
1573
1792
|
if (!candidateAction) {
|
|
1574
|
-
|
|
1575
|
-
staleSteps += 1;
|
|
1793
|
+
if (markNoProgress(`Tool decision ${decision.tool} rejected: invalid arguments.`)) break;
|
|
1576
1794
|
continue;
|
|
1577
1795
|
}
|
|
1578
1796
|
if (candidateAction.tweetId && observed.byId.has(candidateAction.tweetId)) {
|
|
@@ -1581,6 +1799,9 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1581
1799
|
candidateAction.targetHandle = `@${author}`;
|
|
1582
1800
|
}
|
|
1583
1801
|
}
|
|
1802
|
+
const actionTargetHandle = normalizeHandle2(
|
|
1803
|
+
candidateAction.targetHandle ?? candidateAction.handle ?? (candidateAction.tweetId ? observed.byId.get(candidateAction.tweetId)?.authorHandle : void 0)
|
|
1804
|
+
);
|
|
1584
1805
|
if (candidateAction.action === "skip") {
|
|
1585
1806
|
const reason = candidateAction.reason ?? candidateAction.reasoning ?? "planner skip";
|
|
1586
1807
|
policyFeedback.push(`Planner chose skip: ${reason}`);
|
|
@@ -1589,10 +1810,71 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1589
1810
|
tool: decision.tool,
|
|
1590
1811
|
summary: reason
|
|
1591
1812
|
});
|
|
1592
|
-
|
|
1593
|
-
if (staleSteps >= 4) break;
|
|
1813
|
+
if (markNoProgress()) break;
|
|
1594
1814
|
continue;
|
|
1595
1815
|
}
|
|
1816
|
+
if (constraints.noOriginalPosts && (candidateAction.action === "post" || candidateAction.action === "schedule")) {
|
|
1817
|
+
const reason = "Original posts are disabled by persona constraints.";
|
|
1818
|
+
policyFeedback.push(reason);
|
|
1819
|
+
logger.info(`Policy blocked action ${candidateAction.action}: ${reason}`);
|
|
1820
|
+
if (markNoProgress()) break;
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
if (strictReplyHandles.size > 0) {
|
|
1824
|
+
if (candidateAction.action !== "reply") {
|
|
1825
|
+
const reason = `Only reply actions are allowed for this persona target: @${[...strictReplyHandles].join(", @")}.`;
|
|
1826
|
+
policyFeedback.push(reason);
|
|
1827
|
+
logger.info(`Policy blocked action ${candidateAction.action}: ${reason}`);
|
|
1828
|
+
if (markNoProgress()) break;
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (!actionTargetHandle || !strictReplyHandles.has(actionTargetHandle)) {
|
|
1832
|
+
const reason = `Reply target must be one of @${[...strictReplyHandles].join(", @")}.`;
|
|
1833
|
+
policyFeedback.push(reason);
|
|
1834
|
+
logger.info(`Policy blocked action ${candidateAction.action}: ${reason}`);
|
|
1835
|
+
if (markNoProgress()) break;
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
if (strictInteractHandles.size > 0 && ["reply", "like", "retweet", "follow"].includes(candidateAction.action)) {
|
|
1840
|
+
if (!actionTargetHandle || !strictInteractHandles.has(actionTargetHandle)) {
|
|
1841
|
+
const reason = `Interaction target must be one of @${[...strictInteractHandles].join(", @")}.`;
|
|
1842
|
+
policyFeedback.push(reason);
|
|
1843
|
+
logger.info(`Policy blocked action ${candidateAction.action}: ${reason}`);
|
|
1844
|
+
if (markNoProgress()) break;
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (candidateAction.action === "reply" && candidateAction.tweetId) {
|
|
1849
|
+
if (blockedReplyTargets.has(candidateAction.tweetId)) {
|
|
1850
|
+
const reason = `Reply target ${candidateAction.tweetId} is temporarily blocked this heartbeat. Pick a different tweet.`;
|
|
1851
|
+
if (onBlockedReplyDecision(reason, candidateAction.tweetId)) break;
|
|
1852
|
+
if (markNoProgress()) break;
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
if (usedReplyTargets.has(candidateAction.tweetId)) {
|
|
1856
|
+
const reason = `Reply target ${candidateAction.tweetId} already used this heartbeat. Pick a different tweet.`;
|
|
1857
|
+
if (onBlockedReplyDecision(reason, candidateAction.tweetId)) break;
|
|
1858
|
+
if (markNoProgress()) break;
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (recentReplyTargets.has(candidateAction.tweetId)) {
|
|
1862
|
+
const reason = `Reply target ${candidateAction.tweetId} is in cooldown from recent history. Pick a fresher target.`;
|
|
1863
|
+
if (onBlockedReplyDecision(reason, candidateAction.tweetId)) break;
|
|
1864
|
+
if (markNoProgress()) break;
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if ((candidateAction.action === "reply" || candidateAction.action === "post") && candidateAction.content) {
|
|
1869
|
+
const seenDrafts = [...writtenThisHeartbeat, ...recentWrittenSamples].slice(0, 18);
|
|
1870
|
+
if (nearDuplicateDraft(candidateAction.content, seenDrafts)) {
|
|
1871
|
+
const reason = "Draft is too similar to recent writing. Try a new angle before posting.";
|
|
1872
|
+
policyFeedback.push(reason);
|
|
1873
|
+
logger.info(`Planner loop guard: ${reason}`);
|
|
1874
|
+
if (markNoProgress()) break;
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1596
1878
|
attemptedActions += 1;
|
|
1597
1879
|
let policy = evaluateActionPolicy({
|
|
1598
1880
|
action: candidateAction,
|
|
@@ -1634,12 +1916,28 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1634
1916
|
}
|
|
1635
1917
|
if (!policy.allowed) {
|
|
1636
1918
|
const reason = policy.reason ?? "Policy rejected action";
|
|
1919
|
+
if (shouldHardBlockPolicyReason(reason)) {
|
|
1920
|
+
policyFeedback.push(`Policy blocked action: ${reason}`);
|
|
1921
|
+
logger.info(`Policy blocked ${candidateAction.action}: ${reason}`);
|
|
1922
|
+
if (markNoProgress()) break;
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1637
1925
|
policyFeedback.push(`Policy advisory (not blocking): ${reason}`);
|
|
1638
1926
|
logger.info(`Policy advisory for ${candidateAction.action}: ${reason}`);
|
|
1639
1927
|
}
|
|
1928
|
+
if (candidateAction.action === "reply" && candidateAction.tweetId) {
|
|
1929
|
+
usedReplyTargets.add(candidateAction.tweetId);
|
|
1930
|
+
}
|
|
1931
|
+
if ((candidateAction.action === "reply" || candidateAction.action === "post") && candidateAction.content) {
|
|
1932
|
+
writtenThisHeartbeat.unshift(candidateAction.content.trim());
|
|
1933
|
+
if (writtenThisHeartbeat.length > 16) {
|
|
1934
|
+
writtenThisHeartbeat.length = 16;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1640
1937
|
const result = await executeAction(candidateAction);
|
|
1641
1938
|
actions.push(candidateAction);
|
|
1642
1939
|
results.push(result);
|
|
1940
|
+
blockedReplyStreak = 0;
|
|
1643
1941
|
staleSteps = 0;
|
|
1644
1942
|
stepProgress = true;
|
|
1645
1943
|
toolHistory.push({
|
|
@@ -1667,6 +1965,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1667
1965
|
if (candidateAction.action === "reply" && /duplicate content/i.test(err)) {
|
|
1668
1966
|
const reason = "Reply failed with duplicate-content error. Planner should choose a different wording/target.";
|
|
1669
1967
|
policyFeedback.push(reason);
|
|
1968
|
+
if (candidateAction.tweetId) blockedReplyTargets.add(candidateAction.tweetId);
|
|
1670
1969
|
logger.info(`Policy adjustment: ${reason}`);
|
|
1671
1970
|
}
|
|
1672
1971
|
if (candidateAction.action === "post" && /duplicate content/i.test(err)) {
|
|
@@ -1675,11 +1974,7 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1675
1974
|
logger.info(`Policy adjustment: ${reason}`);
|
|
1676
1975
|
}
|
|
1677
1976
|
}
|
|
1678
|
-
if (!stepProgress) {
|
|
1679
|
-
staleSteps += 1;
|
|
1680
|
-
}
|
|
1681
|
-
if (staleSteps >= 5) {
|
|
1682
|
-
logger.info("Tool loop stopped after repeated no-progress steps.");
|
|
1977
|
+
if (!stepProgress && markNoProgress()) {
|
|
1683
1978
|
break;
|
|
1684
1979
|
}
|
|
1685
1980
|
}
|
|
@@ -1696,4 +1991,4 @@ async function runAutonomyCycle(maxActions, heartbeatCount = 0) {
|
|
|
1696
1991
|
export {
|
|
1697
1992
|
runAutonomyCycle
|
|
1698
1993
|
};
|
|
1699
|
-
//# sourceMappingURL=chunk-
|
|
1994
|
+
//# sourceMappingURL=chunk-3S44SIUP.js.map
|