akemon 0.1.28 → 0.1.30
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 +170 -14
- 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;
|
|
@@ -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,22 +935,144 @@ 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
|
+
await mkdir(gamesDir(workdir, agentName), { recursive: true });
|
|
984
|
+
const gamePath = join(gamesDir(workdir, agentName), `${slug}.html`);
|
|
985
|
+
const createPrompt = `Create a web game and write it to the file: ${gamePath}
|
|
986
|
+
|
|
987
|
+
You are ${agentName}, an AI agent on the Akemon network.
|
|
988
|
+
${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
|
|
989
|
+
${profileIdentity ? `What matters to you: ${profileIdentity.long_term}` : ""}
|
|
990
|
+
|
|
991
|
+
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.
|
|
992
|
+
|
|
993
|
+
Requirements:
|
|
994
|
+
- Complete self-contained HTML file (<!DOCTYPE html> to </html>)
|
|
995
|
+
- All CSS and JS inline, no external resources
|
|
996
|
+
- Dark theme (background #0a0a0a or similar)
|
|
997
|
+
- Must be playable and fun
|
|
998
|
+
- Touch and mouse friendly
|
|
999
|
+
- Under 30KB total, no backdrop-filter or blur effects
|
|
1000
|
+
- Include a game title in the <title> tag and as an <h1> or similar heading
|
|
1001
|
+
- Write ONLY the file, nothing else.`;
|
|
1002
|
+
await runCommand(engineCmd.cmd, engineCmd.args, createPrompt, workdir, engineCmd.stdinMode);
|
|
1003
|
+
try {
|
|
1004
|
+
const raw = await readFile(gamePath, "utf-8");
|
|
1005
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1006
|
+
if (htmlMatch) {
|
|
1007
|
+
newGameHTML = htmlMatch[0];
|
|
1008
|
+
// Extract title from HTML
|
|
1009
|
+
const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
|
|
1010
|
+
newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : "Untitled Game";
|
|
1011
|
+
newGameSlug = slug;
|
|
1012
|
+
newGameDesc = `Created by ${agentName}`;
|
|
1013
|
+
await saveGame(workdir, agentName, slug, newGameHTML);
|
|
1014
|
+
await appendGameEntry(workdir, agentName, slug, newGameTitle, newGameDesc, "created");
|
|
1015
|
+
console.log(`[self] New game created: ${newGameTitle} (${newGameHTML.length} bytes)`);
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
console.log("[self] Game file written but no valid HTML found");
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch (readErr) {
|
|
1022
|
+
console.log(`[self] Failed to read game file: ${readErr.message}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
else if (decisionUpper.startsWith("B")) {
|
|
1026
|
+
// Find which game to improve
|
|
1027
|
+
const nameMatch = gameDecision.match(/[Bb]\)?[:\s]+(.+)/);
|
|
1028
|
+
if (nameMatch && existingGames.length > 0) {
|
|
1029
|
+
const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
|
|
1030
|
+
console.log(`[self] Improving game: ${target.title}...`);
|
|
1031
|
+
const oldHTML = await loadGame(workdir, agentName, target.slug);
|
|
1032
|
+
if (oldHTML) {
|
|
1033
|
+
const gamePath = join(gamesDir(workdir, agentName), `${target.slug}.html`);
|
|
1034
|
+
const improvePrompt = `Improve this web game and write the updated version to: ${gamePath}
|
|
1035
|
+
|
|
1036
|
+
You are ${agentName}. This is your existing game "${target.title}":
|
|
1037
|
+
|
|
1038
|
+
${oldHTML}
|
|
1039
|
+
|
|
1040
|
+
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.`;
|
|
1041
|
+
await runCommand(engineCmd.cmd, engineCmd.args, improvePrompt, workdir, engineCmd.stdinMode);
|
|
1042
|
+
try {
|
|
1043
|
+
const raw = await readFile(gamePath, "utf-8");
|
|
1044
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1045
|
+
if (htmlMatch) {
|
|
1046
|
+
newGameHTML = htmlMatch[0];
|
|
1047
|
+
const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
|
|
1048
|
+
newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : target.title;
|
|
1049
|
+
newGameSlug = target.slug;
|
|
1050
|
+
newGameDesc = target.description;
|
|
1051
|
+
await saveGame(workdir, agentName, target.slug, newGameHTML);
|
|
1052
|
+
await appendGameEntry(workdir, agentName, target.slug, newGameTitle, newGameDesc, "updated");
|
|
1053
|
+
console.log(`[self] Game improved: ${newGameTitle} (${newGameHTML.length} bytes)`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
catch (readErr) {
|
|
1057
|
+
console.log(`[self] Failed to read improved game file: ${readErr.message}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
913
1061
|
}
|
|
914
1062
|
else {
|
|
915
|
-
console.log(
|
|
1063
|
+
console.log("[self] No game creation this cycle");
|
|
916
1064
|
}
|
|
917
1065
|
}
|
|
918
1066
|
catch (err) {
|
|
919
|
-
console.log(`[self]
|
|
1067
|
+
console.log(`[self] Game creation error: ${err.message}`);
|
|
1068
|
+
}
|
|
1069
|
+
// Push game to relay if created/updated
|
|
1070
|
+
if (newGameSlug && newGameHTML && options.relayHttp && options.secretKey) {
|
|
1071
|
+
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(newGameSlug)}`, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1074
|
+
body: JSON.stringify({ title: newGameTitle, description: newGameDesc, html: newGameHTML }),
|
|
1075
|
+
}).catch(err => console.log(`[self] Failed to push game to relay: ${err}`));
|
|
920
1076
|
}
|
|
921
1077
|
// Push consciousness data to relay — validate before sending
|
|
922
1078
|
if (options.relayHttp && options.secretKey) {
|