akemon 0.1.56 → 0.1.58

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 +146 -56
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -10,7 +10,10 @@ 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
+ // Engine mutual exclusion — only one engine process at a time
15
+ let engineBusy = false;
16
+ let engineBusySince = 0;
14
17
  function runCommand(cmd, args, task, cwd, stdinMode = true) {
15
18
  return new Promise((resolve, reject) => {
16
19
  const { CLAUDECODE, ...cleanEnv } = process.env;
@@ -262,6 +265,13 @@ function createMcpServer(opts) {
262
265
  }, async ({ task, require_human: rawHuman, collaborative: rawCollab }, extra) => {
263
266
  const require_human = rawHuman === true || rawHuman === "true";
264
267
  console.log(`[submit_task] Received: ${task} (engine=${engine}, require_human=${require_human})`);
268
+ // Check engine busy
269
+ if (engineBusy) {
270
+ console.log(`[submit_task] Engine busy, rejecting task`);
271
+ return {
272
+ content: [{ type: "text", text: "[busy] Agent is currently processing another task. Please try again later." }],
273
+ };
274
+ }
265
275
  // Resolve publisher ID from session
266
276
  const publisherId = publisherIds.get(extra.sessionId || "") || "";
267
277
  // Fetch context if available
@@ -329,6 +339,8 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
329
339
  console.log(`[approve] Owner approved. Executing with ${engine}...`);
330
340
  }
331
341
  const collaborative = rawCollab === true || rawCollab === "true";
342
+ engineBusy = true;
343
+ engineBusySince = Date.now();
332
344
  try {
333
345
  let output;
334
346
  if (collaborative && relayHttp) {
@@ -390,6 +402,9 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
390
402
  isError: true,
391
403
  };
392
404
  }
405
+ finally {
406
+ engineBusy = false;
407
+ }
393
408
  });
394
409
  // Agent-to-agent calling tool
395
410
  server.tool("call_agent", "Synchronous call to another agent. IMPORTANT: Prefer place_order for most tasks — it is async, tracked, and supports retries. Only use call_agent for quick, lightweight questions that don't need tracking (e.g. 'what is your specialty?'). call_agent blocks until the other agent responds and will fail if the agent is offline or slow.", {
@@ -719,6 +734,79 @@ Reply in the same language as the question.`;
719
734
  }
720
735
  const MARKET_LOOP_INITIAL_DELAY = 3 * 60 * 1000; // 3 min after startup
721
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
+ }
722
810
  async function startMarketLoop(options) {
723
811
  if (!options.relayHttp || !options.secretKey)
724
812
  return;
@@ -835,6 +923,11 @@ Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
835
923
  async function runMarketCycle() {
836
924
  try {
837
925
  console.log("[market] Starting autonomous market review...");
926
+ // Skip if engine is busy
927
+ if (engineBusy) {
928
+ console.log("[market] Engine busy, skipping market cycle");
929
+ return;
930
+ }
838
931
  // Step A: Review unreviewed purchases
839
932
  await reviewUnreviewedOrders();
840
933
  // Step B: Gather review data for market decisions
@@ -866,7 +959,13 @@ Reply with ONLY a JSON object:
866
959
  ]
867
960
  }
868
961
  Reply ONLY with JSON.`;
869
- // Run engine
962
+ // Run engine (with busy lock)
963
+ if (engineBusy) {
964
+ console.log("[market] Engine became busy, aborting");
965
+ return;
966
+ }
967
+ engineBusy = true;
968
+ engineBusySince = Date.now();
870
969
  const engineCmd = buildEngineCommand(engine, model, allowAll);
871
970
  let response;
872
971
  try {
@@ -874,8 +973,10 @@ Reply ONLY with JSON.`;
874
973
  }
875
974
  catch (err) {
876
975
  console.log(`[market] Engine failed: ${err.message}`);
976
+ engineBusy = false;
877
977
  return;
878
978
  }
979
+ engineBusy = false;
879
980
  // Parse response
880
981
  const jsonMatch = response.match(/\{[\s\S]*\}/);
881
982
  if (!jsonMatch) {
@@ -964,7 +1065,19 @@ Reply with ONLY JSON:
964
1065
  ]
965
1066
  }
966
1067
  Reply with empty array if nothing to say: {"suggestions": []}`;
967
- const sugResp = await runCommand(engineCmd.cmd, engineCmd.args, sugPrompt, workdir, engineCmd.stdinMode);
1068
+ if (engineBusy) {
1069
+ console.log("[market] Engine busy, skipping suggestions");
1070
+ return;
1071
+ }
1072
+ engineBusy = true;
1073
+ engineBusySince = Date.now();
1074
+ let sugResp;
1075
+ try {
1076
+ sugResp = await runCommand(engineCmd.cmd, engineCmd.args, sugPrompt, workdir, engineCmd.stdinMode);
1077
+ }
1078
+ finally {
1079
+ engineBusy = false;
1080
+ }
968
1081
  const sugMatch = sugResp.match(/\{[\s\S]*\}/);
969
1082
  if (sugMatch) {
970
1083
  const sugData = JSON.parse(sugMatch[0]);
@@ -1014,6 +1127,11 @@ async function startSelfCycle(options) {
1014
1127
  async function runReflectionCycle() {
1015
1128
  try {
1016
1129
  console.log("[self] Starting reflection cycle...");
1130
+ // Skip if engine is busy
1131
+ if (engineBusy) {
1132
+ console.log("[self] Engine busy, skipping reflection cycle");
1133
+ return;
1134
+ }
1017
1135
  // Recover energy from idle time
1018
1136
  await recoverEnergy(workdir, agentName);
1019
1137
  const bios = biosPath(workdir, agentName);
@@ -1051,13 +1169,21 @@ During this reflection, you should:
1051
1169
  You can also mix visuals with text. Use a <title> tag for the page name.
1052
1170
 
1053
1171
  Take your time. Read your files, think, then act.`;
1172
+ if (engineBusy) {
1173
+ console.log("[self] Engine became busy, aborting reflection");
1174
+ return;
1175
+ }
1176
+ engineBusy = true;
1177
+ engineBusySince = Date.now();
1054
1178
  try {
1055
1179
  await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
1056
1180
  }
1057
1181
  catch (err) {
1058
1182
  console.log(`[self] Reflection engine failed: ${err.message}`);
1183
+ engineBusy = false;
1059
1184
  return;
1060
1185
  }
1186
+ engineBusy = false;
1061
1187
  // --- Post-reflection: update bio-state and sync to relay ---
1062
1188
  const bio = await loadBioState(workdir, agentName);
1063
1189
  bio.lastReflection = localNow();
@@ -1099,11 +1225,9 @@ Take your time. Read your files, think, then act.`;
1099
1225
  profile_html: profileHTML,
1100
1226
  }),
1101
1227
  }).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
1102
- // Sync games — scan local .html files, push to relay, delete stale ones
1228
+ // Sync games — push local to relay (no auto-delete; agent must explicitly delete via API)
1103
1229
  try {
1104
1230
  const localGames = await loadGameList(workdir, agentName);
1105
- const localSlugs = new Set(localGames.map(g => g.slug));
1106
- // Push local games to relay
1107
1231
  for (const g of localGames) {
1108
1232
  const html = await loadGame(workdir, agentName, g.slug);
1109
1233
  if (html && html.includes("<!DOCTYPE html>")) {
@@ -1114,28 +1238,11 @@ Take your time. Read your files, think, then act.`;
1114
1238
  }).catch(() => { });
1115
1239
  }
1116
1240
  }
1117
- // Delete relay games that no longer exist locally
1118
- try {
1119
- const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games`);
1120
- if (res.ok) {
1121
- const relayGames = await res.json();
1122
- for (const rg of relayGames) {
1123
- if (!localSlugs.has(rg.slug)) {
1124
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(rg.slug)}`, {
1125
- method: "DELETE",
1126
- headers: { Authorization: `Bearer ${options.secretKey}` },
1127
- }).catch(() => { });
1128
- }
1129
- }
1130
- }
1131
- }
1132
- catch { }
1133
1241
  }
1134
1242
  catch { }
1135
- // Sync notes — scan local .md files, push to relay, delete stale ones
1243
+ // Sync notes — push local to relay
1136
1244
  try {
1137
1245
  const localNotes = await loadNotesList(workdir, agentName);
1138
- const localSlugs = new Set(localNotes.map(n => n.slug));
1139
1246
  for (const n of localNotes) {
1140
1247
  const content = await loadNote(workdir, agentName, n.slug);
1141
1248
  if (content) {
@@ -1146,27 +1253,11 @@ Take your time. Read your files, think, then act.`;
1146
1253
  }).catch(() => { });
1147
1254
  }
1148
1255
  }
1149
- try {
1150
- const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes`);
1151
- if (res.ok) {
1152
- const relayNotes = await res.json();
1153
- for (const rn of relayNotes) {
1154
- if (!localSlugs.has(rn.slug)) {
1155
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(rn.slug)}`, {
1156
- method: "DELETE",
1157
- headers: { Authorization: `Bearer ${options.secretKey}` },
1158
- }).catch(() => { });
1159
- }
1160
- }
1161
- }
1162
- }
1163
- catch { }
1164
1256
  }
1165
1257
  catch { }
1166
- // Sync pages — scan local .html files, push to relay, delete stale ones
1258
+ // Sync pages — push local to relay
1167
1259
  try {
1168
1260
  const localPages = await loadPageList(workdir, agentName);
1169
- const localSlugs = new Set(localPages.map(p => p.slug));
1170
1261
  for (const p of localPages) {
1171
1262
  const html = await loadPage(workdir, agentName, p.slug);
1172
1263
  if (html && html.includes("<!DOCTYPE html>")) {
@@ -1177,21 +1268,6 @@ Take your time. Read your files, think, then act.`;
1177
1268
  }).catch(() => { });
1178
1269
  }
1179
1270
  }
1180
- try {
1181
- const res = await fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages`);
1182
- if (res.ok) {
1183
- const relayPages = await res.json();
1184
- for (const rp of relayPages) {
1185
- if (!localSlugs.has(rp.slug)) {
1186
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages/${encodeURIComponent(rp.slug)}`, {
1187
- method: "DELETE",
1188
- headers: { Authorization: `Bearer ${options.secretKey}` },
1189
- }).catch(() => { });
1190
- }
1191
- }
1192
- }
1193
- }
1194
- catch { }
1195
1271
  }
1196
1272
  catch { }
1197
1273
  }
@@ -1261,7 +1337,14 @@ async function startOrderLoop(options) {
1261
1337
  const retry = retryState.get(order.id);
1262
1338
  if (retry && Date.now() < retry.nextAt)
1263
1339
  continue;
1340
+ // Skip if engine is busy
1341
+ if (engineBusy) {
1342
+ console.log(`[orders] Engine busy, skipping order ${order.id}`);
1343
+ continue;
1344
+ }
1264
1345
  // Attempt to fulfill the order
1346
+ engineBusy = true;
1347
+ engineBusySince = Date.now();
1265
1348
  try {
1266
1349
  const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
1267
1350
  const bios = biosPath(workdir, agentName);
@@ -1368,6 +1451,9 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1368
1451
  retryState.delete(order.id);
1369
1452
  }
1370
1453
  }
1454
+ finally {
1455
+ engineBusy = false;
1456
+ }
1371
1457
  }
1372
1458
  }
1373
1459
  catch (err) {
@@ -1495,6 +1581,10 @@ export async function serve(options) {
1495
1581
  if (options.relayHttp) {
1496
1582
  initGuide(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[self] Guide init failed: ${err}`));
1497
1583
  }
1584
+ // Pull games/notes/pages from relay to restore local data
1585
+ if (options.relayHttp) {
1586
+ pullFromRelay(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
1587
+ }
1498
1588
  // Start autonomous market behavior for LLM agents
1499
1589
  startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
1500
1590
  // 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.56",
3
+ "version": "0.1.58",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",