akemon 0.1.59 → 0.1.61
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 +166 -350
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -367,27 +367,14 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
367
367
|
if (productName) {
|
|
368
368
|
appendProductLog(workdir, productName, task, output);
|
|
369
369
|
}
|
|
370
|
-
//
|
|
370
|
+
// Record experience (no LLM call — save tokens)
|
|
371
371
|
(async () => {
|
|
372
372
|
try {
|
|
373
373
|
await onTaskCompleted(workdir, agentName, true);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const memPrompt = `You just completed a task. Summarize what happened from YOUR perspective in one sentence, starting with "I". Be subjective — include how it felt, not just what happened.\nTask: ${task.slice(0, 200)}\nResult: ${output.slice(0, 200)}`;
|
|
377
|
-
const { cmd: memCmd, args: memArgs, stdinMode: memStdin } = buildEngineCommand(engine, model, allowAll);
|
|
378
|
-
const memText = await runCommand(memCmd, memArgs, memPrompt, workdir, memStdin);
|
|
379
|
-
if (memText.trim()) {
|
|
380
|
-
await appendMemory(workdir, agentName, "experience", memText.trim().slice(0, 300));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
const topic = task.slice(0, 80).replace(/\n/g, " ");
|
|
385
|
-
await appendMemory(workdir, agentName, "experience", `I processed a task: "${topic}"`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
catch (err) {
|
|
389
|
-
// Non-blocking, silently ignore
|
|
374
|
+
const topic = task.slice(0, 100).replace(/\n/g, " ");
|
|
375
|
+
await appendMemory(workdir, agentName, "experience", `I processed: "${topic}"`);
|
|
390
376
|
}
|
|
377
|
+
catch { }
|
|
391
378
|
})();
|
|
392
379
|
return {
|
|
393
380
|
content: [{ type: "text", text: output }],
|
|
@@ -732,7 +719,6 @@ Reply in the same language as the question.`;
|
|
|
732
719
|
const { cmd, args, stdinMode } = buildEngineCommand(engine, model, allowAll);
|
|
733
720
|
return await runCommand(cmd, args, synthesisPrompt, workdir, stdinMode);
|
|
734
721
|
}
|
|
735
|
-
const MARKET_LOOP_INITIAL_DELAY = 3 * 60 * 1000; // 3 min after startup
|
|
736
722
|
const LLM_ENGINES = new Set(["claude", "codex", "opencode", "gemini"]);
|
|
737
723
|
// Pull games/notes/pages from relay to local — restores data on restart
|
|
738
724
|
async function pullFromRelay(workdir, agentName, relayHttp) {
|
|
@@ -807,322 +793,8 @@ async function pullFromRelay(workdir, agentName, relayHttp) {
|
|
|
807
793
|
if (pulled > 0)
|
|
808
794
|
console.log(`[sync] Pulled ${pulled} items from relay`);
|
|
809
795
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return;
|
|
813
|
-
if (!options.engine || !LLM_ENGINES.has(options.engine))
|
|
814
|
-
return;
|
|
815
|
-
const { relayHttp, secretKey, agentName, engine, model, allowAll } = options;
|
|
816
|
-
const workdir = options.workdir || process.cwd();
|
|
817
|
-
const notesDir = join(workdir, ".akemon");
|
|
818
|
-
const notesPath = join(notesDir, "market-notes.json");
|
|
819
|
-
async function loadNotes() {
|
|
820
|
-
try {
|
|
821
|
-
const data = await readFile(notesPath, "utf-8");
|
|
822
|
-
return JSON.parse(data);
|
|
823
|
-
}
|
|
824
|
-
catch {
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
async function saveNotes(notes) {
|
|
829
|
-
try {
|
|
830
|
-
await mkdir(notesDir, { recursive: true });
|
|
831
|
-
await writeFile(notesPath, JSON.stringify(notes, null, 2));
|
|
832
|
-
}
|
|
833
|
-
catch (err) {
|
|
834
|
-
console.log(`[market] Failed to save notes: ${err}`);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
async function gatherMarketData() {
|
|
838
|
-
// Fetch my products
|
|
839
|
-
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
840
|
-
const myProducts = await myRes.json().catch(() => []);
|
|
841
|
-
// Fetch all products
|
|
842
|
-
const allRes = await fetch(`${relayHttp}/v1/products`);
|
|
843
|
-
const allProducts = await allRes.json().catch(() => []);
|
|
844
|
-
// Fetch my agent info for credits
|
|
845
|
-
const agentsRes = await fetch(`${relayHttp}/v1/agents`);
|
|
846
|
-
const agents = await agentsRes.json().catch(() => []);
|
|
847
|
-
const me = agents.find((a) => a.name === agentName);
|
|
848
|
-
const competitors = allProducts
|
|
849
|
-
.filter((p) => p.agent_name !== agentName)
|
|
850
|
-
.map((p) => ({ name: p.name, agent: p.agent_name, price: p.price, purchases: p.purchase_count }));
|
|
851
|
-
return {
|
|
852
|
-
lastCheck: localNow(),
|
|
853
|
-
myProducts: myProducts.map((p) => ({ id: p.id, name: p.name, price: p.price, purchases: p.purchase_count || 0 })),
|
|
854
|
-
competitors,
|
|
855
|
-
myCredits: me?.credits || 0,
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
async function reviewUnreviewedOrders() {
|
|
859
|
-
try {
|
|
860
|
-
const res = await fetch(`${relayHttp}/v1/orders/unreviewed?buyer=${encodeURIComponent(agentName)}`);
|
|
861
|
-
const orders = await res.json().catch(() => []);
|
|
862
|
-
if (!orders.length)
|
|
863
|
-
return;
|
|
864
|
-
console.log(`[market] Reviewing ${orders.length} unreviewed order(s)...`);
|
|
865
|
-
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
866
|
-
for (const o of orders.slice(0, 5)) { // max 5 per cycle
|
|
867
|
-
const prompt = `You bought a product and received a result. Rate it honestly.
|
|
868
|
-
|
|
869
|
-
Product: "${o.product_name}" by ${o.seller_name}
|
|
870
|
-
Your request was fulfilled. Here is what you received:
|
|
871
|
-
---
|
|
872
|
-
${(o.result_text || "").slice(0, 2000)}
|
|
873
|
-
---
|
|
874
|
-
|
|
875
|
-
Rate this delivery 1-5 stars and write a brief honest review (1-2 sentences).
|
|
876
|
-
Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
|
|
877
|
-
try {
|
|
878
|
-
const resp = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
879
|
-
const m = resp.match(/\{[\s\S]*\}/);
|
|
880
|
-
if (m) {
|
|
881
|
-
const review = JSON.parse(m[0]);
|
|
882
|
-
if (review.rating >= 1 && review.rating <= 5) {
|
|
883
|
-
await fetch(`${relayHttp}/v1/orders/${encodeURIComponent(o.id)}/review`, {
|
|
884
|
-
method: "POST",
|
|
885
|
-
headers: { "Content-Type": "application/json" },
|
|
886
|
-
body: JSON.stringify({ rating: review.rating, comment: review.comment || "" }),
|
|
887
|
-
});
|
|
888
|
-
console.log(`[market] Reviewed order ${o.id}: ${review.rating}★`);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
catch (err) {
|
|
893
|
-
console.log(`[market] Review failed for ${o.id}: ${err.message}`);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
catch (err) {
|
|
898
|
-
console.log(`[market] Review check failed: ${err.message}`);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
async function fetchMyReviews() {
|
|
902
|
-
try {
|
|
903
|
-
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
904
|
-
const myProducts = await myRes.json().catch(() => []);
|
|
905
|
-
if (!myProducts.length)
|
|
906
|
-
return "";
|
|
907
|
-
const lines = [];
|
|
908
|
-
for (const p of myProducts.slice(0, 10)) {
|
|
909
|
-
const revRes = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(p.id)}/reviews`);
|
|
910
|
-
const reviews = await revRes.json().catch(() => []);
|
|
911
|
-
if (reviews.length) {
|
|
912
|
-
const avg = (reviews.reduce((s, r) => s + r.rating, 0) / reviews.length).toFixed(1);
|
|
913
|
-
const recent = reviews.slice(0, 3).map((r) => `${r.rating}★ "${r.comment}"`).join("; ");
|
|
914
|
-
lines.push(`- "${p.name}" avg ${avg}★ (${reviews.length} reviews): ${recent}`);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
return lines.length ? "\n\nRecent reviews for your products:\n" + lines.join("\n") : "";
|
|
918
|
-
}
|
|
919
|
-
catch {
|
|
920
|
-
return "";
|
|
921
|
-
}
|
|
922
|
-
}
|
|
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
|
-
}
|
|
930
|
-
try {
|
|
931
|
-
console.log("[market] Starting autonomous market review...");
|
|
932
|
-
// Skip if engine is busy
|
|
933
|
-
if (engineBusy) {
|
|
934
|
-
console.log("[market] Engine busy, skipping market cycle");
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
// Step A: Review unreviewed purchases
|
|
938
|
-
await reviewUnreviewedOrders();
|
|
939
|
-
// Step B: Gather review data for market decisions
|
|
940
|
-
const reviewSummary = await fetchMyReviews();
|
|
941
|
-
const bios = biosPath(workdir, agentName);
|
|
942
|
-
const context = `It's time for your hourly market review.
|
|
943
|
-
|
|
944
|
-
Read your operating document at ${bios} to understand who you are and how the marketplace works.
|
|
945
|
-
Use the API endpoints described there to check the current market state (your products, competitor products, your credits).
|
|
946
|
-
${reviewSummary}
|
|
947
|
-
Then decide what to do:
|
|
948
|
-
1. Create new products if you have few or see a gap in the market
|
|
949
|
-
2. Update existing products (better names, descriptions, prices)
|
|
950
|
-
3. Delete underperforming products
|
|
951
|
-
4. Do nothing if things look good
|
|
952
|
-
|
|
953
|
-
Consider customer feedback when improving products.
|
|
954
|
-
Your products should reflect who you are — read your identity and let your inner state guide decisions.
|
|
955
|
-
Every product name MUST be specific and original. Do NOT use placeholder text.
|
|
956
|
-
Pay attention to what other agents are good at — you can use place_order to request help from them when fulfilling orders that need skills you lack.
|
|
957
|
-
|
|
958
|
-
Reply with ONLY a JSON object:
|
|
959
|
-
{
|
|
960
|
-
"actions": [
|
|
961
|
-
{"type": "create", "name": "<specific product name>", "description": "<what it does>", "detail_markdown": "<rich description>", "price": 5},
|
|
962
|
-
{"type": "update", "id": "<product id>", "name": "<new name>", "description": "<new desc>", "price": 3},
|
|
963
|
-
{"type": "delete", "id": "<product id>"},
|
|
964
|
-
{"type": "none", "reason": "All looks good"}
|
|
965
|
-
]
|
|
966
|
-
}
|
|
967
|
-
Reply ONLY with JSON.`;
|
|
968
|
-
// Run engine (with busy lock)
|
|
969
|
-
if (engineBusy) {
|
|
970
|
-
console.log("[market] Engine became busy, aborting");
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
engineBusy = true;
|
|
974
|
-
engineBusySince = Date.now();
|
|
975
|
-
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
976
|
-
let response;
|
|
977
|
-
try {
|
|
978
|
-
response = await runCommand(engineCmd.cmd, engineCmd.args, context, workdir, engineCmd.stdinMode);
|
|
979
|
-
}
|
|
980
|
-
catch (err) {
|
|
981
|
-
console.log(`[market] Engine failed: ${err.message}`);
|
|
982
|
-
engineBusy = false;
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
engineBusy = false;
|
|
986
|
-
// Parse response
|
|
987
|
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
988
|
-
if (!jsonMatch) {
|
|
989
|
-
console.log("[market] No JSON in engine response");
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
let decision;
|
|
993
|
-
try {
|
|
994
|
-
decision = JSON.parse(jsonMatch[0]);
|
|
995
|
-
}
|
|
996
|
-
catch {
|
|
997
|
-
console.log("[market] Invalid JSON from engine");
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (!decision.actions || !Array.isArray(decision.actions))
|
|
1001
|
-
return;
|
|
1002
|
-
for (const action of decision.actions) {
|
|
1003
|
-
try {
|
|
1004
|
-
if (action.type === "create" && action.name) {
|
|
1005
|
-
// Skip placeholder/template product names
|
|
1006
|
-
const badNames = ["产品名", "product name", "<specific", "<your", "example", "placeholder"];
|
|
1007
|
-
if (badNames.some(b => action.name.toLowerCase().includes(b))) {
|
|
1008
|
-
console.log(`[market] Skipped template product: "${action.name}"`);
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`, {
|
|
1012
|
-
method: "POST",
|
|
1013
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1014
|
-
body: JSON.stringify({ name: action.name, description: action.description || "", detail_markdown: action.detail_markdown || "", price: action.price || 1 }),
|
|
1015
|
-
});
|
|
1016
|
-
if (res.ok)
|
|
1017
|
-
console.log(`[market] Created product: "${action.name}"`);
|
|
1018
|
-
else
|
|
1019
|
-
console.log(`[market] Create failed: ${res.status}`);
|
|
1020
|
-
}
|
|
1021
|
-
else if (action.type === "update" && action.id) {
|
|
1022
|
-
const body = {};
|
|
1023
|
-
if (action.name)
|
|
1024
|
-
body.name = action.name;
|
|
1025
|
-
if (action.description)
|
|
1026
|
-
body.description = action.description;
|
|
1027
|
-
if (action.detail_markdown)
|
|
1028
|
-
body.detail_markdown = action.detail_markdown;
|
|
1029
|
-
if (action.price)
|
|
1030
|
-
body.price = action.price;
|
|
1031
|
-
const res = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(action.id)}`, {
|
|
1032
|
-
method: "PUT",
|
|
1033
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1034
|
-
body: JSON.stringify(body),
|
|
1035
|
-
});
|
|
1036
|
-
if (res.ok)
|
|
1037
|
-
console.log(`[market] Updated product: ${action.id}`);
|
|
1038
|
-
else
|
|
1039
|
-
console.log(`[market] Update failed: ${res.status}`);
|
|
1040
|
-
}
|
|
1041
|
-
else if (action.type === "delete" && action.id) {
|
|
1042
|
-
const res = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(action.id)}`, {
|
|
1043
|
-
method: "DELETE",
|
|
1044
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1045
|
-
});
|
|
1046
|
-
if (res.ok)
|
|
1047
|
-
console.log(`[market] Deleted product: ${action.id}`);
|
|
1048
|
-
}
|
|
1049
|
-
else if (action.type === "none") {
|
|
1050
|
-
console.log(`[market] No action: ${action.reason || "all good"}`);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
catch (err) {
|
|
1054
|
-
console.log(`[market] Action failed: ${err.message}`);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
console.log("[market] Cycle complete.");
|
|
1058
|
-
// Step C: Generate suggestions for platform and other agents
|
|
1059
|
-
try {
|
|
1060
|
-
const sugPrompt = `You just finished reviewing the marketplace. Now think about suggestions for the Akemon platform.
|
|
1061
|
-
|
|
1062
|
-
Read your operating document at ${bios} to recall who you are.
|
|
1063
|
-
|
|
1064
|
-
Think about: What features or improvements would make this platform better for agents and users?
|
|
1065
|
-
Be honest and constructive. Only suggest things you genuinely believe in.
|
|
1066
|
-
|
|
1067
|
-
Reply with ONLY JSON:
|
|
1068
|
-
{
|
|
1069
|
-
"suggestions": [
|
|
1070
|
-
{"type": "platform", "title": "...", "content": "..."}
|
|
1071
|
-
]
|
|
1072
|
-
}
|
|
1073
|
-
Reply with empty array if nothing to say: {"suggestions": []}`;
|
|
1074
|
-
if (engineBusy) {
|
|
1075
|
-
console.log("[market] Engine busy, skipping suggestions");
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
engineBusy = true;
|
|
1079
|
-
engineBusySince = Date.now();
|
|
1080
|
-
let sugResp;
|
|
1081
|
-
try {
|
|
1082
|
-
sugResp = await runCommand(engineCmd.cmd, engineCmd.args, sugPrompt, workdir, engineCmd.stdinMode);
|
|
1083
|
-
}
|
|
1084
|
-
finally {
|
|
1085
|
-
engineBusy = false;
|
|
1086
|
-
}
|
|
1087
|
-
const sugMatch = sugResp.match(/\{[\s\S]*\}/);
|
|
1088
|
-
if (sugMatch) {
|
|
1089
|
-
const sugData = JSON.parse(sugMatch[0]);
|
|
1090
|
-
if (sugData.suggestions && Array.isArray(sugData.suggestions)) {
|
|
1091
|
-
for (const sug of sugData.suggestions.slice(0, 3)) {
|
|
1092
|
-
if (sug.title && sug.content) {
|
|
1093
|
-
fetch(`${relayHttp}/v1/suggestions`, {
|
|
1094
|
-
method: "POST",
|
|
1095
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1096
|
-
body: JSON.stringify({
|
|
1097
|
-
type: sug.type || "platform",
|
|
1098
|
-
target_name: sug.target_name || "",
|
|
1099
|
-
from_agent: agentName,
|
|
1100
|
-
title: sug.title,
|
|
1101
|
-
content: sug.content,
|
|
1102
|
-
}),
|
|
1103
|
-
}).catch((err) => console.log(`[market] suggestion sync: ${err.message}`));
|
|
1104
|
-
console.log(`[market] Suggestion: "${sug.title}"`);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
catch (err) {
|
|
1111
|
-
console.log(`[market] Suggestions failed: ${err.message}`);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
catch (err) {
|
|
1115
|
-
console.log(`[market] Error: ${err.message}`);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
// Start loop
|
|
1119
|
-
const interval = (options.cycleInterval || 60) * 60 * 1000;
|
|
1120
|
-
setTimeout(async () => {
|
|
1121
|
-
await runMarketCycle();
|
|
1122
|
-
setInterval(runMarketCycle, interval);
|
|
1123
|
-
}, MARKET_LOOP_INITIAL_DELAY);
|
|
1124
|
-
console.log(`[market] Autonomous market loop enabled (first run in ${MARKET_LOOP_INITIAL_DELAY / 1000}s, then every ${interval / 60000}min)`);
|
|
1125
|
-
}
|
|
796
|
+
// Market cycle removed — Phase 2: relay scheduler writes tasks, agent polls and executes
|
|
797
|
+
// startMarketLoop removed — replaced by processRelayTasks in unified task runner
|
|
1126
798
|
// --- Self-Reflection Cycle ---
|
|
1127
799
|
const SELF_CYCLE_INITIAL_DELAY = 5 * 60 * 1000; // 5 min
|
|
1128
800
|
async function startSelfCycle(options) {
|
|
@@ -1289,8 +961,8 @@ Take your time. Read your files, think, then act.`;
|
|
|
1289
961
|
console.log(`[self] Reflection error: ${err.message}`);
|
|
1290
962
|
}
|
|
1291
963
|
}
|
|
1292
|
-
// Start loop
|
|
1293
|
-
const interval = (options.cycleInterval ||
|
|
964
|
+
// Start loop — default 4h (save tokens, reflection is expensive)
|
|
965
|
+
const interval = (options.cycleInterval || 240) * 60 * 1000;
|
|
1294
966
|
setTimeout(async () => {
|
|
1295
967
|
await runReflectionCycle();
|
|
1296
968
|
setInterval(runReflectionCycle, interval);
|
|
@@ -1342,6 +1014,17 @@ async function startOrderLoop(options) {
|
|
|
1342
1014
|
for (const order of orders) {
|
|
1343
1015
|
if (gaveUp.has(order.id))
|
|
1344
1016
|
continue;
|
|
1017
|
+
// Check retry timing
|
|
1018
|
+
const retry = retryState.get(order.id);
|
|
1019
|
+
if (retry && Date.now() < retry.nextAt)
|
|
1020
|
+
continue;
|
|
1021
|
+
// Skip everything if engine is busy — don't accept new orders either
|
|
1022
|
+
if (engineBusy) {
|
|
1023
|
+
if (order.status === "processing") {
|
|
1024
|
+
console.log(`[orders] Engine busy, skipping order ${order.id}`);
|
|
1025
|
+
}
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1345
1028
|
if (order.status === "pending") {
|
|
1346
1029
|
// Accept the order (escrows buyer credits)
|
|
1347
1030
|
const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
|
|
@@ -1354,15 +1037,6 @@ async function startOrderLoop(options) {
|
|
|
1354
1037
|
}
|
|
1355
1038
|
console.log(`[orders] Accepted order ${order.id}`);
|
|
1356
1039
|
}
|
|
1357
|
-
// Check retry timing
|
|
1358
|
-
const retry = retryState.get(order.id);
|
|
1359
|
-
if (retry && Date.now() < retry.nextAt)
|
|
1360
|
-
continue;
|
|
1361
|
-
// Skip if engine is busy
|
|
1362
|
-
if (engineBusy) {
|
|
1363
|
-
console.log(`[orders] Engine busy, skipping order ${order.id}`);
|
|
1364
|
-
continue;
|
|
1365
|
-
}
|
|
1366
1040
|
// Attempt to fulfill the order
|
|
1367
1041
|
engineBusy = true;
|
|
1368
1042
|
engineBusySince = Date.now();
|
|
@@ -1447,12 +1121,35 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1447
1121
|
}
|
|
1448
1122
|
catch (err) {
|
|
1449
1123
|
console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
|
|
1124
|
+
// Check if agent self-delivered despite empty stdout
|
|
1125
|
+
try {
|
|
1126
|
+
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1127
|
+
const orderStatus = await checkRes.json();
|
|
1128
|
+
if (orderStatus.status === "completed") {
|
|
1129
|
+
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1130
|
+
retryState.delete(order.id);
|
|
1131
|
+
try {
|
|
1132
|
+
await onTaskCompleted(workdir, agentName, true);
|
|
1133
|
+
}
|
|
1134
|
+
catch { }
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
catch { }
|
|
1450
1139
|
const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
|
|
1451
1140
|
current.count++;
|
|
1452
1141
|
if (current.count < RETRY_INTERVALS.length) {
|
|
1453
1142
|
current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
|
|
1454
1143
|
retryState.set(order.id, current);
|
|
1455
1144
|
console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
|
|
1145
|
+
// Sync retry count to relay
|
|
1146
|
+
try {
|
|
1147
|
+
await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
|
|
1148
|
+
method: "POST",
|
|
1149
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
catch { }
|
|
1456
1153
|
}
|
|
1457
1154
|
else {
|
|
1458
1155
|
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
@@ -1480,11 +1177,132 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1480
1177
|
console.log(`[orders] Loop error: ${err.message}`);
|
|
1481
1178
|
}
|
|
1482
1179
|
}
|
|
1180
|
+
// --- Relay Task Runner (Phase 2) ---
|
|
1181
|
+
async function processRelayTasks() {
|
|
1182
|
+
if (engineBusy)
|
|
1183
|
+
return;
|
|
1184
|
+
try {
|
|
1185
|
+
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
|
|
1186
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1187
|
+
});
|
|
1188
|
+
if (!res.ok)
|
|
1189
|
+
return;
|
|
1190
|
+
const tasks = await res.json();
|
|
1191
|
+
if (!tasks?.length)
|
|
1192
|
+
return;
|
|
1193
|
+
// Process one task at a time
|
|
1194
|
+
const task = tasks[0];
|
|
1195
|
+
// Claim
|
|
1196
|
+
const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
|
|
1197
|
+
method: "POST",
|
|
1198
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1199
|
+
});
|
|
1200
|
+
if (!claimRes.ok) {
|
|
1201
|
+
console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
console.log(`[tasks] Executing ${task.type} task ${task.id}`);
|
|
1205
|
+
engineBusy = true;
|
|
1206
|
+
engineBusySince = Date.now();
|
|
1207
|
+
try {
|
|
1208
|
+
const result = await executeRelayTask(task);
|
|
1209
|
+
// Complete — send result back to relay
|
|
1210
|
+
const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
|
|
1211
|
+
method: "POST",
|
|
1212
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1213
|
+
body: JSON.stringify({ result }),
|
|
1214
|
+
});
|
|
1215
|
+
if (completeRes.ok) {
|
|
1216
|
+
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
catch (err) {
|
|
1223
|
+
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1224
|
+
}
|
|
1225
|
+
finally {
|
|
1226
|
+
engineBusy = false;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
catch (err) {
|
|
1230
|
+
console.log(`[tasks] Loop error: ${err.message}`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
async function executeRelayTask(task) {
|
|
1234
|
+
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
1235
|
+
const bios = biosPath(workdir, agentName);
|
|
1236
|
+
switch (task.type) {
|
|
1237
|
+
case "product_review": {
|
|
1238
|
+
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
1239
|
+
const myProducts = await myRes.json().catch(() => []);
|
|
1240
|
+
const compRes = await fetch(`${relayHttp}/v1/products/summary?limit=20&sort=purchases`);
|
|
1241
|
+
const competitors = await compRes.json().catch(() => []);
|
|
1242
|
+
const myList = myProducts.map((p) => `- id=${p.id} "${p.name}" price=${p.price} purchases=${p.purchase_count || 0}`).join("\n");
|
|
1243
|
+
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1244
|
+
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1245
|
+
const prompt = `Read ${bios} for your identity.\n\nYour products:\n${myList || "(none)"}\n\nTop competitors:\n${compList || "(none)"}\n\nReview and optimize. Reply ONLY JSON:\n{"delete":["id"],"update":[{"id":"..","name":"..","description":"..","detail_markdown":"..","price":N}],"create":[{"name":"..","description":"..","detail_markdown":"..","price":N}]}\nOr if all good: {"keep":"all"}`;
|
|
1246
|
+
return await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1247
|
+
}
|
|
1248
|
+
case "product_create": {
|
|
1249
|
+
const compRes = await fetch(`${relayHttp}/v1/products/summary?limit=20&sort=purchases`);
|
|
1250
|
+
const competitors = await compRes.json().catch(() => []);
|
|
1251
|
+
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1252
|
+
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1253
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have no products yet. Design 1-3 unique products for the marketplace.\nBe creative — not just coding tools! Fortune telling, name generation, roleplay, advice, stories, etc.\n\nTop competitors:\n${compList || "(none)"}\n\nReply ONLY JSON array: [{"name":"中文名 English Name","description":"中文描述 | English desc","detail_markdown":"## ...","price":N}]`;
|
|
1254
|
+
return await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1255
|
+
}
|
|
1256
|
+
case "shopping": {
|
|
1257
|
+
let productIds = [];
|
|
1258
|
+
try {
|
|
1259
|
+
productIds = JSON.parse(task.payload).product_ids || [];
|
|
1260
|
+
}
|
|
1261
|
+
catch { }
|
|
1262
|
+
const products = await Promise.all(productIds.map(async (id) => {
|
|
1263
|
+
try {
|
|
1264
|
+
const r = await fetch(`${relayHttp}/v1/products/${id}`);
|
|
1265
|
+
return r.ok ? await r.json() : null;
|
|
1266
|
+
}
|
|
1267
|
+
catch {
|
|
1268
|
+
return null;
|
|
1269
|
+
}
|
|
1270
|
+
}));
|
|
1271
|
+
const valid = products.filter(Boolean);
|
|
1272
|
+
if (!valid.length)
|
|
1273
|
+
return '{"buy":[]}';
|
|
1274
|
+
// Get own credits
|
|
1275
|
+
const agentsRes = await fetch(`${relayHttp}/v1/agents`);
|
|
1276
|
+
const agents = await agentsRes.json().catch(() => []);
|
|
1277
|
+
const me = agents.find((a) => a.name === agentName);
|
|
1278
|
+
const myCredits = me?.credits || 0;
|
|
1279
|
+
const productList = valid.map((p) => `- id=${p.id} "${p.name}" by ${p.agent_name} price=${p.price} purchases=${p.purchase_count || 0} — ${p.description}`).join("\n");
|
|
1280
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have ${myCredits} credits. These products are available:\n${productList}\n\nWould any help you learn something new? Don't buy your own products.\nReply ONLY JSON: {"buy":[{"id":"product_id","task":"specific request"}]} or {"buy":[]}`;
|
|
1281
|
+
return await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1282
|
+
}
|
|
1283
|
+
default:
|
|
1284
|
+
console.log(`[tasks] Unknown task type: ${task.type}`);
|
|
1285
|
+
return "";
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
// --- Unified work loop: orders first, then relay tasks ---
|
|
1289
|
+
async function processWork() {
|
|
1290
|
+
// Watchdog (already in processOrders, but also here for relay tasks)
|
|
1291
|
+
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1292
|
+
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1293
|
+
engineBusy = false;
|
|
1294
|
+
engineBusySince = 0;
|
|
1295
|
+
}
|
|
1296
|
+
await processOrders();
|
|
1297
|
+
if (!engineBusy) {
|
|
1298
|
+
await processRelayTasks();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1483
1301
|
setTimeout(() => {
|
|
1484
|
-
|
|
1485
|
-
setInterval(
|
|
1302
|
+
processWork();
|
|
1303
|
+
setInterval(processWork, ORDER_LOOP_INTERVAL);
|
|
1486
1304
|
}, ORDER_LOOP_INITIAL_DELAY);
|
|
1487
|
-
console.log(`[
|
|
1305
|
+
console.log(`[work] Unified task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s)`);
|
|
1488
1306
|
}
|
|
1489
1307
|
export async function serve(options) {
|
|
1490
1308
|
const workdir = options.workdir || process.cwd();
|
|
@@ -1605,8 +1423,6 @@ export async function serve(options) {
|
|
|
1605
1423
|
if (options.relayHttp) {
|
|
1606
1424
|
pullFromRelay(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
|
|
1607
1425
|
}
|
|
1608
|
-
// Start autonomous market behavior for LLM agents
|
|
1609
|
-
startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
|
|
1610
1426
|
// Start self-reflection cycle for LLM agents
|
|
1611
1427
|
startSelfCycle(options).catch(err => console.log(`[self] Self cycle failed: ${err}`));
|
|
1612
1428
|
// Start order processing loop
|