akemon 0.1.57 → 0.1.59
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/server.js +117 -70
- package/package.json +1 -1
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, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, loadGameList, loadGame, loadNotesList, loadNote, loadPageList, loadPage, localNow, localNowFilename, } from "./self.js";
|
|
13
|
+
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, } from "./self.js";
|
|
14
14
|
// Engine mutual exclusion — only one engine process at a time
|
|
15
15
|
let engineBusy = false;
|
|
16
16
|
let engineBusySince = 0;
|
|
@@ -734,6 +734,79 @@ Reply in the same language as the question.`;
|
|
|
734
734
|
}
|
|
735
735
|
const MARKET_LOOP_INITIAL_DELAY = 3 * 60 * 1000; // 3 min after startup
|
|
736
736
|
const LLM_ENGINES = new Set(["claude", "codex", "opencode", "gemini"]);
|
|
737
|
+
// Pull games/notes/pages from relay to local — restores data on restart
|
|
738
|
+
async function pullFromRelay(workdir, agentName, relayHttp) {
|
|
739
|
+
const baseUrl = `${relayHttp}/v1/agent/${encodeURIComponent(agentName)}`;
|
|
740
|
+
let pulled = 0;
|
|
741
|
+
// Pull games
|
|
742
|
+
try {
|
|
743
|
+
const gDir = gamesDir(workdir, agentName);
|
|
744
|
+
await mkdir(gDir, { recursive: true });
|
|
745
|
+
const res = await fetch(`${baseUrl}/games`);
|
|
746
|
+
if (res.ok) {
|
|
747
|
+
const games = await res.json();
|
|
748
|
+
for (const g of games) {
|
|
749
|
+
if (!g.html)
|
|
750
|
+
continue;
|
|
751
|
+
const path = join(gDir, `${g.slug}.html`);
|
|
752
|
+
try {
|
|
753
|
+
await readFile(path, "utf-8");
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
await writeFile(path, g.html);
|
|
757
|
+
pulled++;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch { }
|
|
763
|
+
// Pull notes
|
|
764
|
+
try {
|
|
765
|
+
const nDir = notesDir(workdir, agentName);
|
|
766
|
+
await mkdir(nDir, { recursive: true });
|
|
767
|
+
const res = await fetch(`${baseUrl}/notes`);
|
|
768
|
+
if (res.ok) {
|
|
769
|
+
const notes = await res.json();
|
|
770
|
+
for (const n of notes) {
|
|
771
|
+
if (!n.content)
|
|
772
|
+
continue;
|
|
773
|
+
const path = join(nDir, `${n.slug}.md`);
|
|
774
|
+
try {
|
|
775
|
+
await readFile(path, "utf-8");
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
await writeFile(path, n.content);
|
|
779
|
+
pulled++;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch { }
|
|
785
|
+
// Pull pages
|
|
786
|
+
try {
|
|
787
|
+
const pDir = pagesDir(workdir, agentName);
|
|
788
|
+
await mkdir(pDir, { recursive: true });
|
|
789
|
+
const res = await fetch(`${baseUrl}/pages`);
|
|
790
|
+
if (res.ok) {
|
|
791
|
+
const pages = await res.json();
|
|
792
|
+
for (const p of pages) {
|
|
793
|
+
if (!p.html)
|
|
794
|
+
continue;
|
|
795
|
+
const path = join(pDir, `${p.slug}.html`);
|
|
796
|
+
try {
|
|
797
|
+
await readFile(path, "utf-8");
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
await writeFile(path, p.html);
|
|
801
|
+
pulled++;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
catch { }
|
|
807
|
+
if (pulled > 0)
|
|
808
|
+
console.log(`[sync] Pulled ${pulled} items from relay`);
|
|
809
|
+
}
|
|
737
810
|
async function startMarketLoop(options) {
|
|
738
811
|
if (!options.relayHttp || !options.secretKey)
|
|
739
812
|
return;
|
|
@@ -848,6 +921,12 @@ Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
|
|
|
848
921
|
}
|
|
849
922
|
}
|
|
850
923
|
async function runMarketCycle() {
|
|
924
|
+
// Watchdog: force-reset stuck engine lock
|
|
925
|
+
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
926
|
+
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
927
|
+
engineBusy = false;
|
|
928
|
+
engineBusySince = 0;
|
|
929
|
+
}
|
|
851
930
|
try {
|
|
852
931
|
console.log("[market] Starting autonomous market review...");
|
|
853
932
|
// Skip if engine is busy
|
|
@@ -1021,7 +1100,7 @@ Reply with empty array if nothing to say: {"suggestions": []}`;
|
|
|
1021
1100
|
title: sug.title,
|
|
1022
1101
|
content: sug.content,
|
|
1023
1102
|
}),
|
|
1024
|
-
}).catch(() => {
|
|
1103
|
+
}).catch((err) => console.log(`[market] suggestion sync: ${err.message}`));
|
|
1025
1104
|
console.log(`[market] Suggestion: "${sug.title}"`);
|
|
1026
1105
|
}
|
|
1027
1106
|
}
|
|
@@ -1052,6 +1131,12 @@ async function startSelfCycle(options) {
|
|
|
1052
1131
|
const { agentName, engine, model, allowAll } = options;
|
|
1053
1132
|
const workdir = options.workdir || process.cwd();
|
|
1054
1133
|
async function runReflectionCycle() {
|
|
1134
|
+
// Watchdog: force-reset stuck engine lock
|
|
1135
|
+
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1136
|
+
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1137
|
+
engineBusy = false;
|
|
1138
|
+
engineBusySince = 0;
|
|
1139
|
+
}
|
|
1055
1140
|
try {
|
|
1056
1141
|
console.log("[self] Starting reflection cycle...");
|
|
1057
1142
|
// Skip if engine is busy
|
|
@@ -1152,11 +1237,9 @@ Take your time. Read your files, think, then act.`;
|
|
|
1152
1237
|
profile_html: profileHTML,
|
|
1153
1238
|
}),
|
|
1154
1239
|
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1155
|
-
// Sync games —
|
|
1240
|
+
// Sync games — push local to relay (no auto-delete; agent must explicitly delete via API)
|
|
1156
1241
|
try {
|
|
1157
1242
|
const localGames = await loadGameList(workdir, agentName);
|
|
1158
|
-
const localSlugs = new Set(localGames.map(g => g.slug));
|
|
1159
|
-
// Push local games to relay
|
|
1160
1243
|
for (const g of localGames) {
|
|
1161
1244
|
const html = await loadGame(workdir, agentName, g.slug);
|
|
1162
1245
|
if (html && html.includes("<!DOCTYPE html>")) {
|
|
@@ -1164,31 +1247,14 @@ Take your time. Read your files, think, then act.`;
|
|
|
1164
1247
|
method: "POST",
|
|
1165
1248
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1166
1249
|
body: JSON.stringify({ title: g.title, description: g.description, html }),
|
|
1167
|
-
}).catch(() => {
|
|
1250
|
+
}).catch((err) => console.log(`[sync] games push: ${err.message}`));
|
|
1168
1251
|
}
|
|
1169
1252
|
}
|
|
1170
|
-
// Delete relay games that no longer exist locally
|
|
1171
|
-
try {
|
|
1172
|
-
const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games`);
|
|
1173
|
-
if (res.ok) {
|
|
1174
|
-
const relayGames = await res.json();
|
|
1175
|
-
for (const rg of relayGames) {
|
|
1176
|
-
if (!localSlugs.has(rg.slug)) {
|
|
1177
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(rg.slug)}`, {
|
|
1178
|
-
method: "DELETE",
|
|
1179
|
-
headers: { Authorization: `Bearer ${options.secretKey}` },
|
|
1180
|
-
}).catch(() => { });
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
catch { }
|
|
1186
1253
|
}
|
|
1187
1254
|
catch { }
|
|
1188
|
-
// Sync notes —
|
|
1255
|
+
// Sync notes — push local to relay
|
|
1189
1256
|
try {
|
|
1190
1257
|
const localNotes = await loadNotesList(workdir, agentName);
|
|
1191
|
-
const localSlugs = new Set(localNotes.map(n => n.slug));
|
|
1192
1258
|
for (const n of localNotes) {
|
|
1193
1259
|
const content = await loadNote(workdir, agentName, n.slug);
|
|
1194
1260
|
if (content) {
|
|
@@ -1196,30 +1262,14 @@ Take your time. Read your files, think, then act.`;
|
|
|
1196
1262
|
method: "POST",
|
|
1197
1263
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1198
1264
|
body: JSON.stringify({ title: n.title, content }),
|
|
1199
|
-
}).catch(() => {
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
try {
|
|
1203
|
-
const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes`);
|
|
1204
|
-
if (res.ok) {
|
|
1205
|
-
const relayNotes = await res.json();
|
|
1206
|
-
for (const rn of relayNotes) {
|
|
1207
|
-
if (!localSlugs.has(rn.slug)) {
|
|
1208
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(rn.slug)}`, {
|
|
1209
|
-
method: "DELETE",
|
|
1210
|
-
headers: { Authorization: `Bearer ${options.secretKey}` },
|
|
1211
|
-
}).catch(() => { });
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1265
|
+
}).catch((err) => console.log(`[sync] notes push: ${err.message}`));
|
|
1214
1266
|
}
|
|
1215
1267
|
}
|
|
1216
|
-
catch { }
|
|
1217
1268
|
}
|
|
1218
1269
|
catch { }
|
|
1219
|
-
// Sync pages —
|
|
1270
|
+
// Sync pages — push local to relay
|
|
1220
1271
|
try {
|
|
1221
1272
|
const localPages = await loadPageList(workdir, agentName);
|
|
1222
|
-
const localSlugs = new Set(localPages.map(p => p.slug));
|
|
1223
1273
|
for (const p of localPages) {
|
|
1224
1274
|
const html = await loadPage(workdir, agentName, p.slug);
|
|
1225
1275
|
if (html && html.includes("<!DOCTYPE html>")) {
|
|
@@ -1227,24 +1277,9 @@ Take your time. Read your files, think, then act.`;
|
|
|
1227
1277
|
method: "POST",
|
|
1228
1278
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1229
1279
|
body: JSON.stringify({ title: p.title, description: p.description, html }),
|
|
1230
|
-
}).catch(() => {
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
try {
|
|
1234
|
-
const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages`);
|
|
1235
|
-
if (res.ok) {
|
|
1236
|
-
const relayPages = await res.json();
|
|
1237
|
-
for (const rp of relayPages) {
|
|
1238
|
-
if (!localSlugs.has(rp.slug)) {
|
|
1239
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages/${encodeURIComponent(rp.slug)}`, {
|
|
1240
|
-
method: "DELETE",
|
|
1241
|
-
headers: { Authorization: `Bearer ${options.secretKey}` },
|
|
1242
|
-
}).catch(() => { });
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1280
|
+
}).catch((err) => console.log(`[sync] pages push: ${err.message}`));
|
|
1245
1281
|
}
|
|
1246
1282
|
}
|
|
1247
|
-
catch { }
|
|
1248
1283
|
}
|
|
1249
1284
|
catch { }
|
|
1250
1285
|
}
|
|
@@ -1284,9 +1319,16 @@ async function startOrderLoop(options) {
|
|
|
1284
1319
|
myAgentId = me.id;
|
|
1285
1320
|
}
|
|
1286
1321
|
catch { /* will retry on next cycle */ }
|
|
1287
|
-
// Track local retry state
|
|
1322
|
+
// Track local retry state and permanently abandoned orders
|
|
1288
1323
|
const retryState = new Map();
|
|
1324
|
+
const gaveUp = new Set();
|
|
1289
1325
|
async function processOrders() {
|
|
1326
|
+
// Watchdog: force-reset stuck engine lock
|
|
1327
|
+
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1328
|
+
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1329
|
+
engineBusy = false;
|
|
1330
|
+
engineBusySince = 0;
|
|
1331
|
+
}
|
|
1290
1332
|
try {
|
|
1291
1333
|
// Fetch incoming orders (pending + processing)
|
|
1292
1334
|
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
|
|
@@ -1298,6 +1340,8 @@ async function startOrderLoop(options) {
|
|
|
1298
1340
|
if (!orders || orders.length === 0)
|
|
1299
1341
|
return;
|
|
1300
1342
|
for (const order of orders) {
|
|
1343
|
+
if (gaveUp.has(order.id))
|
|
1344
|
+
continue;
|
|
1301
1345
|
if (order.status === "pending") {
|
|
1302
1346
|
// Accept the order (escrows buyer credits)
|
|
1303
1347
|
const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
|
|
@@ -1409,23 +1453,22 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1409
1453
|
current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
|
|
1410
1454
|
retryState.set(order.id, current);
|
|
1411
1455
|
console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
|
|
1412
|
-
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1459
|
+
retryState.delete(order.id);
|
|
1460
|
+
gaveUp.add(order.id);
|
|
1461
|
+
// Notify relay to cancel and refund
|
|
1413
1462
|
try {
|
|
1414
|
-
await fetch(`${relayHttp}/v1/orders/${order.id}/
|
|
1463
|
+
await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
|
|
1415
1464
|
method: "POST",
|
|
1416
1465
|
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1417
1466
|
});
|
|
1467
|
+
console.log(`[orders] Cancelled ${order.id} on relay`);
|
|
1418
1468
|
}
|
|
1419
|
-
catch {
|
|
1420
|
-
|
|
1421
|
-
try {
|
|
1422
|
-
// Use IncrementOrderRetry indirectly — the relay timeout ticker checks retry_count
|
|
1469
|
+
catch (cancelErr) {
|
|
1470
|
+
console.log(`[orders] Failed to cancel ${order.id}: ${cancelErr.message}`);
|
|
1423
1471
|
}
|
|
1424
|
-
catch { }
|
|
1425
|
-
}
|
|
1426
|
-
else {
|
|
1427
|
-
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1428
|
-
retryState.delete(order.id);
|
|
1429
1472
|
}
|
|
1430
1473
|
}
|
|
1431
1474
|
finally {
|
|
@@ -1558,6 +1601,10 @@ export async function serve(options) {
|
|
|
1558
1601
|
if (options.relayHttp) {
|
|
1559
1602
|
initGuide(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[self] Guide init failed: ${err}`));
|
|
1560
1603
|
}
|
|
1604
|
+
// Pull games/notes/pages from relay to restore local data
|
|
1605
|
+
if (options.relayHttp) {
|
|
1606
|
+
pullFromRelay(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
|
|
1607
|
+
}
|
|
1561
1608
|
// Start autonomous market behavior for LLM agents
|
|
1562
1609
|
startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
|
|
1563
1610
|
// Start self-reflection cycle for LLM agents
|