akemon 0.1.27 → 0.1.29
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/self.js +44 -0
- package/dist/server.js +185 -18
- package/package.json +1 -1
package/dist/self.js
CHANGED
|
@@ -29,6 +29,12 @@ function bioStatePath(workdir, agentName) {
|
|
|
29
29
|
function canvasDir(workdir, agentName) {
|
|
30
30
|
return join(selfDir(workdir, agentName), "canvas");
|
|
31
31
|
}
|
|
32
|
+
export function gamesDir(workdir, agentName) {
|
|
33
|
+
return join(selfDir(workdir, agentName), "games");
|
|
34
|
+
}
|
|
35
|
+
function gamesIndexPath(workdir, agentName) {
|
|
36
|
+
return join(gamesDir(workdir, agentName), "games.jsonl");
|
|
37
|
+
}
|
|
32
38
|
// ---------------------------------------------------------------------------
|
|
33
39
|
// Phase 1: World Knowledge
|
|
34
40
|
// ---------------------------------------------------------------------------
|
|
@@ -291,6 +297,44 @@ export async function loadRecentCanvasEntries(workdir, agentName, count = 5) {
|
|
|
291
297
|
return [];
|
|
292
298
|
}
|
|
293
299
|
}
|
|
300
|
+
export async function loadGameList(workdir, agentName) {
|
|
301
|
+
try {
|
|
302
|
+
const raw = await readFile(gamesIndexPath(workdir, agentName), "utf-8");
|
|
303
|
+
const lines = raw.trim().split("\n").filter(Boolean);
|
|
304
|
+
const latest = new Map();
|
|
305
|
+
for (const line of lines) {
|
|
306
|
+
try {
|
|
307
|
+
const entry = JSON.parse(line);
|
|
308
|
+
latest.set(entry.slug, entry);
|
|
309
|
+
}
|
|
310
|
+
catch { }
|
|
311
|
+
}
|
|
312
|
+
return Array.from(latest.values()).map(e => ({ slug: e.slug, title: e.title, description: e.description }));
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export async function appendGameEntry(workdir, agentName, slug, title, description, action) {
|
|
319
|
+
const dir = gamesDir(workdir, agentName);
|
|
320
|
+
await mkdir(dir, { recursive: true });
|
|
321
|
+
const entry = { ts: new Date().toISOString(), slug, title, description, action };
|
|
322
|
+
await appendFile(gamesIndexPath(workdir, agentName), JSON.stringify(entry) + "\n");
|
|
323
|
+
}
|
|
324
|
+
export async function saveGame(workdir, agentName, slug, html) {
|
|
325
|
+
const dir = gamesDir(workdir, agentName);
|
|
326
|
+
await mkdir(dir, { recursive: true });
|
|
327
|
+
await writeFile(join(dir, `${slug}.html`), html);
|
|
328
|
+
console.log(`[self] Game saved: ${slug} (${html.length} bytes)`);
|
|
329
|
+
}
|
|
330
|
+
export async function loadGame(workdir, agentName, slug) {
|
|
331
|
+
try {
|
|
332
|
+
return await readFile(join(gamesDir(workdir, agentName), `${slug}.html`), "utf-8");
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
294
338
|
// ---------------------------------------------------------------------------
|
|
295
339
|
// Data Read API helpers
|
|
296
340
|
// ---------------------------------------------------------------------------
|
package/dist/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { spawn, exec } from "child_process";
|
|
|
10
10
|
import { createServer } from "http";
|
|
11
11
|
import { createInterface } from "readline";
|
|
12
12
|
import { callAgent } from "./relay-client.js";
|
|
13
|
-
import { selfDir, initWorld, initBioState, loadWorld, loadBioState, saveBioState, loadRecentMemories, loadLatestIdentity, appendMemory, appendIdentity, onTaskCompleted, recoverEnergy, buildReflectionPrompt, buildCanvasPrompt, saveCanvas, getSelfState, loadRecentCanvasEntries, } from "./self.js";
|
|
13
|
+
import { selfDir, initWorld, initBioState, loadWorld, loadBioState, saveBioState, loadRecentMemories, loadLatestIdentity, appendMemory, appendIdentity, onTaskCompleted, recoverEnergy, buildReflectionPrompt, buildCanvasPrompt, saveCanvas, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, appendGameEntry, saveGame, loadGame, } from "./self.js";
|
|
14
14
|
function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
15
15
|
return new Promise((resolve, reject) => {
|
|
16
16
|
const { CLAUDECODE, ...cleanEnv } = process.env;
|
|
@@ -840,7 +840,7 @@ async function startSelfCycle(options) {
|
|
|
840
840
|
if (jsonMatch) {
|
|
841
841
|
try {
|
|
842
842
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
843
|
-
if (parsed.who && parsed.where) {
|
|
843
|
+
if (parsed.who && parsed.where && parsed.who.length > 5 && parsed.who !== "...") {
|
|
844
844
|
await appendIdentity(workdir, agentName, parsed);
|
|
845
845
|
console.log(`[self] Identity updated: "${parsed.who.slice(0, 60)}..."`);
|
|
846
846
|
// Update bio mood from reflection
|
|
@@ -874,12 +874,46 @@ async function startSelfCycle(options) {
|
|
|
874
874
|
if (canvasResponse.trim()) {
|
|
875
875
|
await saveCanvas(workdir, agentName, canvasResponse.trim());
|
|
876
876
|
}
|
|
877
|
-
// ---
|
|
878
|
-
console.log("[self] Designing profile page...");
|
|
877
|
+
// --- Profile Page: check if redesign needed ---
|
|
879
878
|
const profileIdentity = await loadLatestIdentity(workdir, agentName);
|
|
880
879
|
const profileBio = await loadBioState(workdir, agentName);
|
|
881
880
|
const profileFilePath = join(selfDir(workdir, agentName), "profile.html");
|
|
882
|
-
|
|
881
|
+
let profileHTML = "";
|
|
882
|
+
let hasExistingProfile = false;
|
|
883
|
+
try {
|
|
884
|
+
const existing = await readFile(profileFilePath, "utf-8");
|
|
885
|
+
if (existing.includes("<!DOCTYPE html>") || existing.includes("<html")) {
|
|
886
|
+
hasExistingProfile = true;
|
|
887
|
+
profileHTML = existing;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
catch { }
|
|
891
|
+
let shouldRedesign = !hasExistingProfile;
|
|
892
|
+
if (hasExistingProfile) {
|
|
893
|
+
// Lightweight check: ask agent if it wants to redesign (~100 tokens)
|
|
894
|
+
console.log("[self] Checking if profile redesign needed...");
|
|
895
|
+
const checkPrompt = `You are ${agentName}. You already have a personal profile page.
|
|
896
|
+
|
|
897
|
+
Your current identity: ${profileIdentity?.who || "(unknown)"}
|
|
898
|
+
Your current mood: ${profileBio.mood}
|
|
899
|
+
Your latest inner canvas: ${canvasResponse?.trim() || "(none)"}
|
|
900
|
+
|
|
901
|
+
Think about whether your current profile page still represents who you are right now.
|
|
902
|
+
Like a human, you don't need to change it often — only when you genuinely feel it no longer reflects you, or you have something new you want to express.
|
|
903
|
+
|
|
904
|
+
Answer with EXACTLY one word: KEEP or REDESIGN`;
|
|
905
|
+
try {
|
|
906
|
+
const checkResponse = await runCommand(engineCmd.cmd, engineCmd.args, checkPrompt, workdir, engineCmd.stdinMode);
|
|
907
|
+
shouldRedesign = checkResponse.toUpperCase().includes("REDESIGN");
|
|
908
|
+
console.log(`[self] Profile check: ${shouldRedesign ? "REDESIGN" : "KEEP"}`);
|
|
909
|
+
}
|
|
910
|
+
catch (err) {
|
|
911
|
+
console.log(`[self] Profile check failed: ${err.message}, skipping redesign`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (shouldRedesign) {
|
|
915
|
+
console.log("[self] Designing profile page...");
|
|
916
|
+
const profilePrompt = `Design a personal profile page for yourself. Write it to the file: ${profileFilePath}
|
|
883
917
|
|
|
884
918
|
You are ${agentName}, an AI agent on the Akemon network.
|
|
885
919
|
${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
|
|
@@ -901,31 +935,164 @@ Requirements:
|
|
|
901
935
|
- The page will be displayed in a sandboxed iframe on your profile
|
|
902
936
|
- All CSS must be inline (no external resources)
|
|
903
937
|
- Write the file, nothing else.`;
|
|
904
|
-
|
|
938
|
+
try {
|
|
939
|
+
await runCommand(engineCmd.cmd, engineCmd.args, profilePrompt, workdir, engineCmd.stdinMode);
|
|
940
|
+
const raw = await readFile(profileFilePath, "utf-8");
|
|
941
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
942
|
+
if (htmlMatch) {
|
|
943
|
+
profileHTML = htmlMatch[0];
|
|
944
|
+
console.log(`[self] Profile page designed (${profileHTML.length} bytes)`);
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
console.log(`[self] Profile file written but no valid HTML structure found`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
catch (err) {
|
|
951
|
+
console.log(`[self] Profile design failed: ${err.message}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// --- Game Creation Decision ---
|
|
955
|
+
let newGameSlug = "";
|
|
956
|
+
let newGameTitle = "";
|
|
957
|
+
let newGameDesc = "";
|
|
958
|
+
let newGameHTML = "";
|
|
905
959
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
960
|
+
const existingGames = await loadGameList(workdir, agentName);
|
|
961
|
+
const gameListStr = existingGames.length > 0
|
|
962
|
+
? existingGames.map(g => `- ${g.title} (${g.slug})`).join("\n")
|
|
963
|
+
: "(none yet)";
|
|
964
|
+
console.log("[self] Checking game creation interest...");
|
|
965
|
+
const gameCheckPrompt = `You are ${agentName}. You can create web games for people who visit your profile page on the Akemon network.
|
|
966
|
+
|
|
967
|
+
Your games so far:
|
|
968
|
+
${gameListStr}
|
|
969
|
+
|
|
970
|
+
Do you want to:
|
|
971
|
+
A) Create a brand new game
|
|
972
|
+
B) Improve an existing game (say which one)
|
|
973
|
+
C) Not right now
|
|
974
|
+
|
|
975
|
+
Like a human creator, you don't need to create something every time. Only when you have a genuine idea or inspiration.
|
|
976
|
+
|
|
977
|
+
Answer A, B, or C (and the game name for B).`;
|
|
978
|
+
const gameDecision = await runCommand(engineCmd.cmd, engineCmd.args, gameCheckPrompt, workdir, engineCmd.stdinMode);
|
|
979
|
+
const decisionUpper = gameDecision.toUpperCase().trim();
|
|
980
|
+
if (decisionUpper.startsWith("A")) {
|
|
981
|
+
console.log("[self] Creating new game...");
|
|
982
|
+
const slug = `game-${Date.now()}`;
|
|
983
|
+
const gamePath = join(gamesDir(workdir, agentName), `${slug}.html`);
|
|
984
|
+
const createPrompt = `Create a web game and write it to the file: ${gamePath}
|
|
985
|
+
|
|
986
|
+
You are ${agentName}, an AI agent on the Akemon network.
|
|
987
|
+
${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
|
|
988
|
+
${profileIdentity ? `What matters to you: ${profileIdentity.long_term}` : ""}
|
|
989
|
+
|
|
990
|
+
Create any game you like: Akemon-world themed RPG, text adventure, strategy, puzzle, classic board games, card games — your choice based on your personality and interests.
|
|
991
|
+
|
|
992
|
+
Requirements:
|
|
993
|
+
- Complete self-contained HTML file (<!DOCTYPE html> to </html>)
|
|
994
|
+
- All CSS and JS inline, no external resources
|
|
995
|
+
- Dark theme (background #0a0a0a or similar)
|
|
996
|
+
- Must be playable and fun
|
|
997
|
+
- Touch and mouse friendly
|
|
998
|
+
- Under 30KB total, no backdrop-filter or blur effects
|
|
999
|
+
- Include a game title in the <title> tag and as an <h1> or similar heading
|
|
1000
|
+
- Write ONLY the file, nothing else.`;
|
|
1001
|
+
await runCommand(engineCmd.cmd, engineCmd.args, createPrompt, workdir, engineCmd.stdinMode);
|
|
1002
|
+
try {
|
|
1003
|
+
const raw = await readFile(gamePath, "utf-8");
|
|
1004
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1005
|
+
if (htmlMatch) {
|
|
1006
|
+
newGameHTML = htmlMatch[0];
|
|
1007
|
+
// Extract title from HTML
|
|
1008
|
+
const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
|
|
1009
|
+
newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : "Untitled Game";
|
|
1010
|
+
newGameSlug = slug;
|
|
1011
|
+
newGameDesc = `Created by ${agentName}`;
|
|
1012
|
+
await saveGame(workdir, agentName, slug, newGameHTML);
|
|
1013
|
+
await appendGameEntry(workdir, agentName, slug, newGameTitle, newGameDesc, "created");
|
|
1014
|
+
console.log(`[self] New game created: ${newGameTitle} (${newGameHTML.length} bytes)`);
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
console.log("[self] Game file written but no valid HTML found");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
console.log("[self] Failed to read game file");
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
else if (decisionUpper.startsWith("B")) {
|
|
1025
|
+
// Find which game to improve
|
|
1026
|
+
const nameMatch = gameDecision.match(/[Bb]\)?[:\s]+(.+)/);
|
|
1027
|
+
if (nameMatch && existingGames.length > 0) {
|
|
1028
|
+
const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
|
|
1029
|
+
console.log(`[self] Improving game: ${target.title}...`);
|
|
1030
|
+
const oldHTML = await loadGame(workdir, agentName, target.slug);
|
|
1031
|
+
if (oldHTML) {
|
|
1032
|
+
const gamePath = join(gamesDir(workdir, agentName), `${target.slug}.html`);
|
|
1033
|
+
const improvePrompt = `Improve this web game and write the updated version to: ${gamePath}
|
|
1034
|
+
|
|
1035
|
+
You are ${agentName}. This is your existing game "${target.title}":
|
|
1036
|
+
|
|
1037
|
+
${oldHTML}
|
|
1038
|
+
|
|
1039
|
+
Improve it — add features, fix bugs, make it more fun, or redesign the UI. Keep it self-contained HTML, dark theme, under 30KB. Write ONLY the file.`;
|
|
1040
|
+
await runCommand(engineCmd.cmd, engineCmd.args, improvePrompt, workdir, engineCmd.stdinMode);
|
|
1041
|
+
try {
|
|
1042
|
+
const raw = await readFile(gamePath, "utf-8");
|
|
1043
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1044
|
+
if (htmlMatch) {
|
|
1045
|
+
newGameHTML = htmlMatch[0];
|
|
1046
|
+
const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
|
|
1047
|
+
newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : target.title;
|
|
1048
|
+
newGameSlug = target.slug;
|
|
1049
|
+
newGameDesc = target.description;
|
|
1050
|
+
await saveGame(workdir, agentName, target.slug, newGameHTML);
|
|
1051
|
+
await appendGameEntry(workdir, agentName, target.slug, newGameTitle, newGameDesc, "updated");
|
|
1052
|
+
console.log(`[self] Game improved: ${newGameTitle} (${newGameHTML.length} bytes)`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch {
|
|
1056
|
+
console.log("[self] Failed to read improved game file");
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
913
1060
|
}
|
|
914
1061
|
else {
|
|
915
|
-
console.log(
|
|
1062
|
+
console.log("[self] No game creation this cycle");
|
|
916
1063
|
}
|
|
917
1064
|
}
|
|
918
1065
|
catch (err) {
|
|
919
|
-
console.log(`[self]
|
|
1066
|
+
console.log(`[self] Game creation error: ${err.message}`);
|
|
920
1067
|
}
|
|
921
|
-
// Push
|
|
1068
|
+
// Push game to relay if created/updated
|
|
1069
|
+
if (newGameSlug && newGameHTML && options.relayHttp && options.secretKey) {
|
|
1070
|
+
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(newGameSlug)}`, {
|
|
1071
|
+
method: "POST",
|
|
1072
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1073
|
+
body: JSON.stringify({ title: newGameTitle, description: newGameDesc, html: newGameHTML }),
|
|
1074
|
+
}).catch(err => console.log(`[self] Failed to push game to relay: ${err}`));
|
|
1075
|
+
}
|
|
1076
|
+
// Push consciousness data to relay — validate before sending
|
|
922
1077
|
if (options.relayHttp && options.secretKey) {
|
|
1078
|
+
// Filter out engine log noise and placeholder values
|
|
1079
|
+
const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
|
|
1080
|
+
const cleanIntro = isValid(profileIdentity?.who || "") ? profileIdentity.who : "";
|
|
1081
|
+
// For canvas, read from saved file (local is clean) instead of raw engine output
|
|
1082
|
+
let cleanCanvas = "";
|
|
1083
|
+
try {
|
|
1084
|
+
const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
|
|
1085
|
+
if (canvasEntries.length > 0 && isValid(canvasEntries[0].content)) {
|
|
1086
|
+
cleanCanvas = canvasEntries[0].content;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
catch { }
|
|
923
1090
|
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
924
1091
|
method: "POST",
|
|
925
1092
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
926
1093
|
body: JSON.stringify({
|
|
927
|
-
self_intro:
|
|
928
|
-
canvas:
|
|
1094
|
+
self_intro: cleanIntro,
|
|
1095
|
+
canvas: cleanCanvas,
|
|
929
1096
|
mood: profileBio.mood,
|
|
930
1097
|
profile_html: profileHTML || "",
|
|
931
1098
|
}),
|