akemon 0.1.55 → 0.1.57

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/cli.js CHANGED
@@ -59,7 +59,7 @@ program
59
59
  mcpServer: opts.mcpServer,
60
60
  cycleInterval: parseInt(opts.interval),
61
61
  });
62
- console.log(``);
62
+ console.log(`\nakemon v${pkg.version}`);
63
63
  if (!opts.public) {
64
64
  console.log(`Access key: ${credentials.accessKey} (share with publishers)`);
65
65
  }
package/dist/server.js CHANGED
@@ -11,6 +11,9 @@ import { createServer } from "http";
11
11
  import { createInterface } from "readline";
12
12
  import { callAgent } from "./relay-client.js";
13
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";
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.", {
@@ -835,6 +850,11 @@ Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
835
850
  async function runMarketCycle() {
836
851
  try {
837
852
  console.log("[market] Starting autonomous market review...");
853
+ // Skip if engine is busy
854
+ if (engineBusy) {
855
+ console.log("[market] Engine busy, skipping market cycle");
856
+ return;
857
+ }
838
858
  // Step A: Review unreviewed purchases
839
859
  await reviewUnreviewedOrders();
840
860
  // Step B: Gather review data for market decisions
@@ -866,7 +886,13 @@ Reply with ONLY a JSON object:
866
886
  ]
867
887
  }
868
888
  Reply ONLY with JSON.`;
869
- // Run engine
889
+ // Run engine (with busy lock)
890
+ if (engineBusy) {
891
+ console.log("[market] Engine became busy, aborting");
892
+ return;
893
+ }
894
+ engineBusy = true;
895
+ engineBusySince = Date.now();
870
896
  const engineCmd = buildEngineCommand(engine, model, allowAll);
871
897
  let response;
872
898
  try {
@@ -874,8 +900,10 @@ Reply ONLY with JSON.`;
874
900
  }
875
901
  catch (err) {
876
902
  console.log(`[market] Engine failed: ${err.message}`);
903
+ engineBusy = false;
877
904
  return;
878
905
  }
906
+ engineBusy = false;
879
907
  // Parse response
880
908
  const jsonMatch = response.match(/\{[\s\S]*\}/);
881
909
  if (!jsonMatch) {
@@ -964,7 +992,19 @@ Reply with ONLY JSON:
964
992
  ]
965
993
  }
966
994
  Reply with empty array if nothing to say: {"suggestions": []}`;
967
- const sugResp = await runCommand(engineCmd.cmd, engineCmd.args, sugPrompt, workdir, engineCmd.stdinMode);
995
+ if (engineBusy) {
996
+ console.log("[market] Engine busy, skipping suggestions");
997
+ return;
998
+ }
999
+ engineBusy = true;
1000
+ engineBusySince = Date.now();
1001
+ let sugResp;
1002
+ try {
1003
+ sugResp = await runCommand(engineCmd.cmd, engineCmd.args, sugPrompt, workdir, engineCmd.stdinMode);
1004
+ }
1005
+ finally {
1006
+ engineBusy = false;
1007
+ }
968
1008
  const sugMatch = sugResp.match(/\{[\s\S]*\}/);
969
1009
  if (sugMatch) {
970
1010
  const sugData = JSON.parse(sugMatch[0]);
@@ -1014,6 +1054,11 @@ async function startSelfCycle(options) {
1014
1054
  async function runReflectionCycle() {
1015
1055
  try {
1016
1056
  console.log("[self] Starting reflection cycle...");
1057
+ // Skip if engine is busy
1058
+ if (engineBusy) {
1059
+ console.log("[self] Engine busy, skipping reflection cycle");
1060
+ return;
1061
+ }
1017
1062
  // Recover energy from idle time
1018
1063
  await recoverEnergy(workdir, agentName);
1019
1064
  const bios = biosPath(workdir, agentName);
@@ -1051,13 +1096,21 @@ During this reflection, you should:
1051
1096
  You can also mix visuals with text. Use a <title> tag for the page name.
1052
1097
 
1053
1098
  Take your time. Read your files, think, then act.`;
1099
+ if (engineBusy) {
1100
+ console.log("[self] Engine became busy, aborting reflection");
1101
+ return;
1102
+ }
1103
+ engineBusy = true;
1104
+ engineBusySince = Date.now();
1054
1105
  try {
1055
1106
  await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
1056
1107
  }
1057
1108
  catch (err) {
1058
1109
  console.log(`[self] Reflection engine failed: ${err.message}`);
1110
+ engineBusy = false;
1059
1111
  return;
1060
1112
  }
1113
+ engineBusy = false;
1061
1114
  // --- Post-reflection: update bio-state and sync to relay ---
1062
1115
  const bio = await loadBioState(workdir, agentName);
1063
1116
  bio.lastReflection = localNow();
@@ -1261,7 +1314,14 @@ async function startOrderLoop(options) {
1261
1314
  const retry = retryState.get(order.id);
1262
1315
  if (retry && Date.now() < retry.nextAt)
1263
1316
  continue;
1317
+ // Skip if engine is busy
1318
+ if (engineBusy) {
1319
+ console.log(`[orders] Engine busy, skipping order ${order.id}`);
1320
+ continue;
1321
+ }
1264
1322
  // Attempt to fulfill the order
1323
+ engineBusy = true;
1324
+ engineBusySince = Date.now();
1265
1325
  try {
1266
1326
  const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
1267
1327
  const bios = biosPath(workdir, agentName);
@@ -1368,6 +1428,9 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1368
1428
  retryState.delete(order.id);
1369
1429
  }
1370
1430
  }
1431
+ finally {
1432
+ engineBusy = false;
1433
+ }
1371
1434
  }
1372
1435
  }
1373
1436
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",