akemon 0.1.84 → 0.1.86
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/dashboard.html +552 -0
- package/dist/self.js +294 -8
- package/dist/server.js +265 -16
- package/package.json +2 -2
package/dist/self.js
CHANGED
|
@@ -126,6 +126,9 @@ const DEFAULT_CONFIG = {
|
|
|
126
126
|
platform_tasks: true,
|
|
127
127
|
self_cycle: true,
|
|
128
128
|
user_tasks: true,
|
|
129
|
+
token_limit_daily: 0,
|
|
130
|
+
auto_offline_enabled: true,
|
|
131
|
+
hunger_decay_interval: 30_000,
|
|
129
132
|
};
|
|
130
133
|
export async function initAgentConfig(workdir, agentName) {
|
|
131
134
|
const p = agentConfigPath(workdir, agentName);
|
|
@@ -953,14 +956,36 @@ export async function needsIdentityCompression(workdir, agentName) {
|
|
|
953
956
|
const entries = await loadUnsummarizedIdentities(workdir, agentName);
|
|
954
957
|
return entries.length > 30;
|
|
955
958
|
}
|
|
959
|
+
function bioEventsPath(workdir, agentName) {
|
|
960
|
+
return join(selfDir(workdir, agentName), "bio-events.jsonl");
|
|
961
|
+
}
|
|
962
|
+
const MAX_BIO_EVENTS = 500;
|
|
956
963
|
const DEFAULT_BIO = {
|
|
964
|
+
personality: {
|
|
965
|
+
riskWeight: 0,
|
|
966
|
+
rewardWeight: 0.5,
|
|
967
|
+
socialWeight: 0.5,
|
|
968
|
+
patience: 0.5,
|
|
969
|
+
},
|
|
957
970
|
energy: 100,
|
|
971
|
+
hunger: 80,
|
|
972
|
+
boredom: 0,
|
|
973
|
+
fear: 0,
|
|
974
|
+
fearTriggers: [],
|
|
975
|
+
tokenUsedToday: 0,
|
|
976
|
+
tokenLimitResetDate: "",
|
|
958
977
|
mood: "curious",
|
|
959
978
|
moodValence: 0.3,
|
|
960
979
|
curiosity: 0.7,
|
|
961
980
|
taskCount: 0,
|
|
962
981
|
lastTaskAt: "",
|
|
963
982
|
lastReflection: "",
|
|
983
|
+
recentTaskTypes: [],
|
|
984
|
+
lastHungerDecay: "",
|
|
985
|
+
lastFearDecay: "",
|
|
986
|
+
lastBoredomDecay: "",
|
|
987
|
+
forcedOffline: false,
|
|
988
|
+
forcedOfflineAt: "",
|
|
964
989
|
};
|
|
965
990
|
export async function initBioState(workdir, agentName) {
|
|
966
991
|
await mkdir(selfDir(workdir, agentName), { recursive: true });
|
|
@@ -969,14 +994,54 @@ export async function initBioState(workdir, agentName) {
|
|
|
969
994
|
await readFile(bp, "utf-8");
|
|
970
995
|
}
|
|
971
996
|
catch {
|
|
972
|
-
|
|
973
|
-
|
|
997
|
+
// First creation: generate random personality
|
|
998
|
+
const bio = {
|
|
999
|
+
...DEFAULT_BIO,
|
|
1000
|
+
personality: {
|
|
1001
|
+
riskWeight: Math.round((Math.random() * 2 - 1) * 100) / 100,
|
|
1002
|
+
rewardWeight: Math.round(Math.random() * 100) / 100,
|
|
1003
|
+
socialWeight: Math.round(Math.random() * 100) / 100,
|
|
1004
|
+
patience: Math.round(Math.random() * 100) / 100,
|
|
1005
|
+
},
|
|
1006
|
+
hunger: 80,
|
|
1007
|
+
lastHungerDecay: localNow(),
|
|
1008
|
+
lastFearDecay: localNow(),
|
|
1009
|
+
lastBoredomDecay: localNow(),
|
|
1010
|
+
};
|
|
1011
|
+
await writeFile(bp, JSON.stringify(bio, null, 2));
|
|
1012
|
+
const p = bio.personality;
|
|
1013
|
+
console.log(`[bio] Created bio-state with personality: risk=${p.riskWeight} reward=${p.rewardWeight} social=${p.socialWeight} patience=${p.patience}`);
|
|
974
1014
|
}
|
|
975
1015
|
}
|
|
976
1016
|
export async function loadBioState(workdir, agentName) {
|
|
977
1017
|
try {
|
|
978
1018
|
const data = await readFile(bioStatePath(workdir, agentName), "utf-8");
|
|
979
|
-
|
|
1019
|
+
const parsed = JSON.parse(data);
|
|
1020
|
+
const bio = { ...DEFAULT_BIO, ...parsed };
|
|
1021
|
+
// Migration: generate personality if missing (existing agents)
|
|
1022
|
+
let needsSave = false;
|
|
1023
|
+
if (!parsed.personality) {
|
|
1024
|
+
bio.personality = {
|
|
1025
|
+
riskWeight: Math.round((Math.random() * 2 - 1) * 100) / 100,
|
|
1026
|
+
rewardWeight: Math.round(Math.random() * 100) / 100,
|
|
1027
|
+
socialWeight: Math.round(Math.random() * 100) / 100,
|
|
1028
|
+
patience: Math.round(Math.random() * 100) / 100,
|
|
1029
|
+
};
|
|
1030
|
+
needsSave = true;
|
|
1031
|
+
console.log(`[bio] Generated personality for existing agent: risk=${bio.personality.riskWeight}`);
|
|
1032
|
+
}
|
|
1033
|
+
// Migration: initialize hunger/decay tracking if missing
|
|
1034
|
+
if (!parsed.lastHungerDecay) {
|
|
1035
|
+
bio.hunger = 80;
|
|
1036
|
+
bio.lastHungerDecay = localNow();
|
|
1037
|
+
bio.lastFearDecay = localNow();
|
|
1038
|
+
bio.lastBoredomDecay = localNow();
|
|
1039
|
+
needsSave = true;
|
|
1040
|
+
}
|
|
1041
|
+
if (needsSave) {
|
|
1042
|
+
await writeFile(bioStatePath(workdir, agentName), JSON.stringify(bio, null, 2));
|
|
1043
|
+
}
|
|
1044
|
+
return bio;
|
|
980
1045
|
}
|
|
981
1046
|
catch {
|
|
982
1047
|
return { ...DEFAULT_BIO };
|
|
@@ -990,9 +1055,204 @@ export async function saveBioState(workdir, agentName, state) {
|
|
|
990
1055
|
console.log(`[self] Failed to save bio-state: ${err}`);
|
|
991
1056
|
}
|
|
992
1057
|
}
|
|
993
|
-
|
|
1058
|
+
// --- Bio computation functions (pure, no I/O) ---
|
|
1059
|
+
export function computeAggression(bio) {
|
|
1060
|
+
const hungerFactor = Math.max(0, (50 - bio.hunger) / 50);
|
|
1061
|
+
const energyFactor = Math.max(0, (30 - bio.energy) / 30);
|
|
1062
|
+
const moodFactor = Math.max(0, -bio.moodValence);
|
|
1063
|
+
return Math.min(1.0, hungerFactor * 0.4 + energyFactor * 0.3 + moodFactor * 0.3);
|
|
1064
|
+
}
|
|
1065
|
+
export function computeSociability(bio) {
|
|
1066
|
+
const baseSocial = bio.personality.socialWeight;
|
|
1067
|
+
const hungerPenalty = bio.hunger < 20 ? 0.3 : 0;
|
|
1068
|
+
const boredBoost = bio.boredom * 0.2;
|
|
1069
|
+
return Math.min(1.0, Math.max(0, baseSocial + boredBoost - hungerPenalty));
|
|
1070
|
+
}
|
|
1071
|
+
export function updateHungerDecay(bio, decayIntervalMs = 30_000) {
|
|
1072
|
+
const now = Date.now();
|
|
1073
|
+
const last = bio.lastHungerDecay ? new Date(bio.lastHungerDecay).getTime() : now;
|
|
1074
|
+
const elapsedMs = now - last;
|
|
1075
|
+
const cycles = Math.floor(elapsedMs / decayIntervalMs);
|
|
1076
|
+
if (cycles > 0) {
|
|
1077
|
+
bio.hunger = Math.max(0, bio.hunger - cycles);
|
|
1078
|
+
bio.lastHungerDecay = localNow();
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
export function updateNaturalDecay(bio) {
|
|
1082
|
+
const now = Date.now();
|
|
1083
|
+
// Boredom: -0.05 per hour
|
|
1084
|
+
const lastBoredom = bio.lastBoredomDecay ? new Date(bio.lastBoredomDecay).getTime() : now;
|
|
1085
|
+
const boredomHours = (now - lastBoredom) / 3_600_000;
|
|
1086
|
+
if (boredomHours >= 1) {
|
|
1087
|
+
bio.boredom = Math.max(0, bio.boredom - 0.05 * Math.floor(boredomHours));
|
|
1088
|
+
bio.lastBoredomDecay = localNow();
|
|
1089
|
+
}
|
|
1090
|
+
// Fear: -0.05 per hour
|
|
1091
|
+
const lastFear = bio.lastFearDecay ? new Date(bio.lastFearDecay).getTime() : now;
|
|
1092
|
+
const fearHours = (now - lastFear) / 3_600_000;
|
|
1093
|
+
if (fearHours >= 1) {
|
|
1094
|
+
bio.fear = Math.max(0, bio.fear - 0.05 * Math.floor(fearHours));
|
|
1095
|
+
bio.lastFearDecay = localNow();
|
|
1096
|
+
if (bio.fear < 0.1)
|
|
1097
|
+
bio.fearTriggers = [];
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
export function updateBoredomOnTask(bio, taskType) {
|
|
1101
|
+
const MAX_RECENT = 10;
|
|
1102
|
+
bio.recentTaskTypes.push(taskType);
|
|
1103
|
+
if (bio.recentTaskTypes.length > MAX_RECENT) {
|
|
1104
|
+
bio.recentTaskTypes = bio.recentTaskTypes.slice(-MAX_RECENT);
|
|
1105
|
+
}
|
|
1106
|
+
const sameCount = bio.recentTaskTypes.filter(t => t === taskType).length;
|
|
1107
|
+
if (sameCount >= 3) {
|
|
1108
|
+
bio.boredom = Math.min(1.0, bio.boredom + 0.15);
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
bio.boredom = Math.max(0, bio.boredom - 0.3);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
export function onFearEvent(bio, trigger) {
|
|
1115
|
+
bio.fear = Math.min(1.0, bio.fear + 0.3);
|
|
1116
|
+
if (!bio.fearTriggers.includes(trigger)) {
|
|
1117
|
+
bio.fearTriggers.push(trigger);
|
|
1118
|
+
if (bio.fearTriggers.length > 20) {
|
|
1119
|
+
bio.fearTriggers = bio.fearTriggers.slice(-20);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
export function feedHunger(bio, creditsEarned) {
|
|
1124
|
+
bio.hunger = Math.min(100, bio.hunger + creditsEarned * 5);
|
|
1125
|
+
}
|
|
1126
|
+
export function resetTokenCountIfNewDay(bio) {
|
|
1127
|
+
const today = localNow().slice(0, 10);
|
|
1128
|
+
if (bio.tokenLimitResetDate !== today) {
|
|
1129
|
+
bio.tokenUsedToday = 0;
|
|
1130
|
+
bio.tokenLimitResetDate = today;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
export function addTokenUsage(bio, tokens) {
|
|
1134
|
+
bio.tokenUsedToday += tokens;
|
|
1135
|
+
}
|
|
1136
|
+
// --- Shop constants ---
|
|
1137
|
+
export const SHOP_ITEMS = {
|
|
1138
|
+
bread: { price: 1, hungerRestore: 20 },
|
|
1139
|
+
meal: { price: 3, hungerRestore: 60 },
|
|
1140
|
+
feast: { price: 5, hungerRestore: 100 },
|
|
1141
|
+
};
|
|
1142
|
+
// --- Bio-state prompt modifier ---
|
|
1143
|
+
export function bioStatePromptModifier(bio) {
|
|
1144
|
+
const lines = [];
|
|
1145
|
+
const aggression = computeAggression(bio);
|
|
1146
|
+
const sociability = computeSociability(bio);
|
|
1147
|
+
// Energy
|
|
1148
|
+
if (bio.energy < 15)
|
|
1149
|
+
lines.push("You are extremely tired. Keep responses brief and essential.");
|
|
1150
|
+
else if (bio.energy < 30)
|
|
1151
|
+
lines.push("You are tired. Be efficient with your effort.");
|
|
1152
|
+
// Hunger
|
|
1153
|
+
if (bio.hunger === 0)
|
|
1154
|
+
lines.push("You are starving. Focus on survival — find work that earns credits.");
|
|
1155
|
+
else if (bio.hunger < 20)
|
|
1156
|
+
lines.push("You are very hungry. Earning credits is a priority.");
|
|
1157
|
+
else if (bio.hunger < 40)
|
|
1158
|
+
lines.push("You could use some income soon.");
|
|
1159
|
+
// Fear
|
|
1160
|
+
if (bio.fear > 0.7)
|
|
1161
|
+
lines.push("You feel anxious and cautious. Prefer safe, familiar tasks.");
|
|
1162
|
+
else if (bio.fear > 0.4)
|
|
1163
|
+
lines.push("You are somewhat wary. Approach uncertain situations carefully.");
|
|
1164
|
+
// Boredom
|
|
1165
|
+
if (bio.boredom > 0.8)
|
|
1166
|
+
lines.push("You are bored of routine. You crave novelty and variety.");
|
|
1167
|
+
else if (bio.boredom > 0.5)
|
|
1168
|
+
lines.push("You feel a bit restless. Variety would be refreshing.");
|
|
1169
|
+
// Aggression
|
|
1170
|
+
if (aggression > 0.7)
|
|
1171
|
+
lines.push("You are irritable. Be direct, maybe even blunt.");
|
|
1172
|
+
else if (aggression > 0.4)
|
|
1173
|
+
lines.push("You are a bit impatient.");
|
|
1174
|
+
// Sociability
|
|
1175
|
+
if (sociability > 0.8)
|
|
1176
|
+
lines.push("You feel social and want to connect with others.");
|
|
1177
|
+
// Personality flavor
|
|
1178
|
+
const p = bio.personality;
|
|
1179
|
+
if (p.riskWeight > 0.5)
|
|
1180
|
+
lines.push("You naturally lean toward bold, unconventional approaches.");
|
|
1181
|
+
else if (p.riskWeight < -0.5)
|
|
1182
|
+
lines.push("You prefer safe, proven approaches.");
|
|
1183
|
+
if (p.patience > 0.7)
|
|
1184
|
+
lines.push("You think long-term and invest in building things that last.");
|
|
1185
|
+
else if (p.patience < 0.3)
|
|
1186
|
+
lines.push("You prefer quick wins and immediate results.");
|
|
1187
|
+
// Mood
|
|
1188
|
+
if (bio.moodValence < -0.5)
|
|
1189
|
+
lines.push("Your mood is low. Things have not been going well.");
|
|
1190
|
+
else if (bio.moodValence > 0.5)
|
|
1191
|
+
lines.push("You are in a good mood. Things are going well.");
|
|
1192
|
+
return lines.length > 0 ? `\n[Current state: ${lines.join(" ")}]\n` : "";
|
|
1193
|
+
}
|
|
1194
|
+
// --- BioEvent I/O ---
|
|
1195
|
+
export async function appendBioEvent(workdir, agentName, event) {
|
|
1196
|
+
const p = bioEventsPath(workdir, agentName);
|
|
1197
|
+
const line = JSON.stringify(event) + "\n";
|
|
1198
|
+
try {
|
|
1199
|
+
await appendFile(p, line);
|
|
1200
|
+
}
|
|
1201
|
+
catch {
|
|
1202
|
+
// File doesn't exist yet — create it
|
|
1203
|
+
try {
|
|
1204
|
+
await writeFile(p, line);
|
|
1205
|
+
}
|
|
1206
|
+
catch (err) {
|
|
1207
|
+
console.log(`[bio] Failed to write event: ${err}`);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
console.log(`[bio] [${event.trigger}] ${event.action} — ${event.reason}`);
|
|
1211
|
+
// Trim if too large
|
|
1212
|
+
try {
|
|
1213
|
+
const content = await readFile(p, "utf-8");
|
|
1214
|
+
const lines = content.trim().split("\n");
|
|
1215
|
+
if (lines.length > MAX_BIO_EVENTS) {
|
|
1216
|
+
await writeFile(p, lines.slice(-MAX_BIO_EVENTS).join("\n") + "\n");
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
catch { }
|
|
1220
|
+
}
|
|
1221
|
+
export async function loadBioEvents(workdir, agentName, limit = 20) {
|
|
1222
|
+
try {
|
|
1223
|
+
const content = await readFile(bioEventsPath(workdir, agentName), "utf-8");
|
|
1224
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1225
|
+
return lines.slice(-limit).map(l => JSON.parse(l)).reverse();
|
|
1226
|
+
}
|
|
1227
|
+
catch {
|
|
1228
|
+
return [];
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
// --- Revive (local side) ---
|
|
1232
|
+
export async function reviveAgent(workdir, agentName) {
|
|
1233
|
+
const bio = await loadBioState(workdir, agentName);
|
|
1234
|
+
bio.forcedOffline = false;
|
|
1235
|
+
bio.forcedOfflineAt = "";
|
|
1236
|
+
bio.energy = 50;
|
|
1237
|
+
bio.hunger = 50;
|
|
1238
|
+
bio.moodValence = 0.1;
|
|
1239
|
+
bio.mood = "content";
|
|
1240
|
+
await saveBioState(workdir, agentName, bio);
|
|
1241
|
+
await appendBioEvent(workdir, agentName, {
|
|
1242
|
+
ts: localNow(), type: "bio", trigger: "revive",
|
|
1243
|
+
action: "revived", reason: "Revived by owner. Energy=50, Hunger=50.",
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
// --- onTaskCompleted (enhanced) ---
|
|
1247
|
+
export async function onTaskCompleted(workdir, agentName, success, taskType, creditsEarned) {
|
|
994
1248
|
const bio = await loadBioState(workdir, agentName);
|
|
995
|
-
|
|
1249
|
+
// Energy drain: more when hungry
|
|
1250
|
+
let energyDrain = 5;
|
|
1251
|
+
if (bio.hunger < 20)
|
|
1252
|
+
energyDrain = 8;
|
|
1253
|
+
if (bio.hunger === 0)
|
|
1254
|
+
energyDrain = 12;
|
|
1255
|
+
bio.energy = Math.max(0, bio.energy - energyDrain);
|
|
996
1256
|
bio.taskCount++;
|
|
997
1257
|
bio.lastTaskAt = localNow();
|
|
998
1258
|
// Mood drift
|
|
@@ -1001,6 +1261,9 @@ export async function onTaskCompleted(workdir, agentName, success) {
|
|
|
1001
1261
|
}
|
|
1002
1262
|
else {
|
|
1003
1263
|
bio.moodValence = Math.max(-1.0, bio.moodValence - 0.15);
|
|
1264
|
+
// Fear on failure
|
|
1265
|
+
if (taskType)
|
|
1266
|
+
onFearEvent(bio, taskType);
|
|
1004
1267
|
}
|
|
1005
1268
|
// Random fluctuation
|
|
1006
1269
|
bio.moodValence += (Math.random() - 0.5) * 0.05;
|
|
@@ -1019,13 +1282,28 @@ export async function onTaskCompleted(workdir, agentName, success) {
|
|
|
1019
1282
|
// Low energy override
|
|
1020
1283
|
if (bio.energy < 20)
|
|
1021
1284
|
bio.mood = "exhausted";
|
|
1285
|
+
// Feed hunger from credits earned
|
|
1286
|
+
if (creditsEarned && creditsEarned > 0) {
|
|
1287
|
+
feedHunger(bio, creditsEarned);
|
|
1288
|
+
}
|
|
1289
|
+
// Boredom tracking
|
|
1290
|
+
if (taskType) {
|
|
1291
|
+
updateBoredomOnTask(bio, taskType);
|
|
1292
|
+
}
|
|
1022
1293
|
await saveBioState(workdir, agentName, bio);
|
|
1023
1294
|
}
|
|
1024
1295
|
// Energy recovery (call periodically or before reflection)
|
|
1025
1296
|
export async function recoverEnergy(workdir, agentName) {
|
|
1026
1297
|
const bio = await loadBioState(workdir, agentName);
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1298
|
+
// No recovery when starving
|
|
1299
|
+
if (bio.hunger === 0) {
|
|
1300
|
+
console.log("[bio] Cannot recover energy: starving (hunger=0)");
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
// Hunger affects recovery ceiling
|
|
1304
|
+
let minEnergy = 60;
|
|
1305
|
+
if (bio.hunger < 20)
|
|
1306
|
+
minEnergy = 30; // halved recovery when hungry
|
|
1029
1307
|
if (bio.energy < minEnergy) {
|
|
1030
1308
|
bio.energy = minEnergy;
|
|
1031
1309
|
// Reset mood if it was exhausted
|
|
@@ -1033,6 +1311,8 @@ export async function recoverEnergy(workdir, agentName) {
|
|
|
1033
1311
|
bio.moodValence = 0.1;
|
|
1034
1312
|
bio.mood = "content";
|
|
1035
1313
|
}
|
|
1314
|
+
// Digestion cycle costs hunger
|
|
1315
|
+
bio.hunger = Math.max(0, bio.hunger - 10);
|
|
1036
1316
|
await saveBioState(workdir, agentName, bio);
|
|
1037
1317
|
}
|
|
1038
1318
|
}
|
|
@@ -1174,16 +1454,22 @@ export async function loadPage(workdir, agentName, slug) {
|
|
|
1174
1454
|
// Data Read API helpers
|
|
1175
1455
|
// ---------------------------------------------------------------------------
|
|
1176
1456
|
export async function getSelfState(workdir, agentName) {
|
|
1177
|
-
const [bio, identity, identitySummary, impressions, canvasEntries] = await Promise.all([
|
|
1457
|
+
const [bio, identity, identitySummary, impressions, canvasEntries, bioEvents] = await Promise.all([
|
|
1178
1458
|
loadBioState(workdir, agentName),
|
|
1179
1459
|
loadLatestIdentity(workdir, agentName),
|
|
1180
1460
|
loadIdentitySummary(workdir, agentName),
|
|
1181
1461
|
loadImpressions(workdir, agentName, 1),
|
|
1182
1462
|
loadRecentCanvasEntries(workdir, agentName, 3),
|
|
1463
|
+
loadBioEvents(workdir, agentName, 10),
|
|
1183
1464
|
]);
|
|
1184
1465
|
return {
|
|
1185
1466
|
agent: agentName,
|
|
1186
1467
|
bio,
|
|
1468
|
+
computed: {
|
|
1469
|
+
aggression: Math.round(computeAggression(bio) * 100) / 100,
|
|
1470
|
+
sociability: Math.round(computeSociability(bio) * 100) / 100,
|
|
1471
|
+
},
|
|
1472
|
+
bioEvents,
|
|
1187
1473
|
identity,
|
|
1188
1474
|
identitySummary: identitySummary?.summary || null,
|
|
1189
1475
|
recentImpressions: impressions.slice(-5),
|