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.
- package/dist/server.js +146 -56
- 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
|
-
|
|
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 —
|
|
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 —
|
|
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 —
|
|
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
|