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.
Files changed (2) hide show
  1. package/dist/server.js +117 -70
  2. 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 — scan local .html files, push to relay, delete stale ones
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 — scan local .md files, push to relay, delete stale ones
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 — scan local .html files, push to relay, delete stale ones
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
- // Extend timeout on relay side
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}/extend`, {
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
- // Bump retry count on relay
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",