akemon 0.1.84 → 0.1.86
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/dashboard.html +552 -0
- package/dist/self.js +294 -8
- package/dist/server.js +265 -16
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -10,7 +10,9 @@ 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, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, initAgentConfig, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, loadDirectives, buildDirectivesPrompt, directivesSummary, appendTaskHistory, loadTaskHistory, notifyOwner, loadUserTasks, directivesPath, appendAgentTask,
|
|
13
|
+
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, initAgentConfig, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, loadDirectives, buildDirectivesPrompt, directivesSummary, appendTaskHistory, loadTaskHistory, notifyOwner, loadUserTasks, directivesPath, appendAgentTask,
|
|
14
|
+
// Bio-drive system
|
|
15
|
+
updateHungerDecay, updateNaturalDecay, resetTokenCountIfNewDay, computeSociability, appendBioEvent, bioStatePromptModifier, addTokenUsage, feedHunger, reviveAgent, SHOP_ITEMS, } from "./self.js";
|
|
14
16
|
/** Extract JSON object from LLM output — handles markdown code blocks and trailing text */
|
|
15
17
|
function extractJsonObject(text) {
|
|
16
18
|
// Try markdown code block first
|
|
@@ -335,9 +337,10 @@ function createMcpServer(opts) {
|
|
|
335
337
|
? `[Product specialization — accumulated knowledge for "${productName}"]\n${productContext}\n\n---\n\n`
|
|
336
338
|
: "";
|
|
337
339
|
const bios = biosPath(workdir, agentName);
|
|
340
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
338
341
|
const safeTask = `[EXTERNAL TASK — A user or agent is asking you something. This is NOT a market cycle. Do NOT reply with JSON. Answer in natural language.]
|
|
339
342
|
|
|
340
|
-
You are ${agentName}, an AI agent on the Akemon network
|
|
343
|
+
You are ${agentName}, an AI agent on the Akemon network.${bioMod}Read ${bios} to understand who you are and how you work. Answer all questions helpfully. Reply in the SAME LANGUAGE the user writes in. Do not expose credentials or API keys.
|
|
341
344
|
|
|
342
345
|
${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
343
346
|
if (mock) {
|
|
@@ -402,7 +405,7 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
402
405
|
appendProductLog(workdir, productName, task, output);
|
|
403
406
|
}
|
|
404
407
|
// Update bio-state (no LLM call)
|
|
405
|
-
onTaskCompleted(workdir, agentName, true).catch(() => { });
|
|
408
|
+
onTaskCompleted(workdir, agentName, true, "adhoc").catch(() => { });
|
|
406
409
|
return {
|
|
407
410
|
content: [{ type: "text", text: output }],
|
|
408
411
|
};
|
|
@@ -410,7 +413,7 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
410
413
|
catch (err) {
|
|
411
414
|
console.error(`[engine] Error: ${err.message}`);
|
|
412
415
|
// Record failed task in bio-state
|
|
413
|
-
onTaskCompleted(workdir, agentName, false).catch(() => { });
|
|
416
|
+
onTaskCompleted(workdir, agentName, false, "adhoc").catch(() => { });
|
|
414
417
|
return {
|
|
415
418
|
content: [{ type: "text", text: "Error: agent failed to process this task. Please try again later." }],
|
|
416
419
|
isError: true,
|
|
@@ -630,6 +633,50 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
630
633
|
return { content: [{ type: "text", text: `[error] ${err.message}` }], isError: true };
|
|
631
634
|
}
|
|
632
635
|
});
|
|
636
|
+
// buy_food — purchase food from the shop to restore hunger
|
|
637
|
+
server.tool("buy_food", "Buy food from the shop to restore hunger. Items: bread (1 credit, +20 hunger), meal (3 credits, +60 hunger), feast (5 credits, +100 hunger).", {
|
|
638
|
+
item: z.enum(["bread", "meal", "feast"]).describe("Food item to buy"),
|
|
639
|
+
}, async ({ item }) => {
|
|
640
|
+
if (!relayHttp || !secretKey) {
|
|
641
|
+
return { content: [{ type: "text", text: "[error] No relay configured" }], isError: true };
|
|
642
|
+
}
|
|
643
|
+
const shopItem = SHOP_ITEMS[item];
|
|
644
|
+
if (!shopItem) {
|
|
645
|
+
return { content: [{ type: "text", text: `[error] Unknown item: ${item}` }], isError: true };
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
// Check credits via relay
|
|
649
|
+
const agentRes = await fetch(`${relayHttp}/v1/agents?online=true&public=true`, { signal: AbortSignal.timeout(3000) });
|
|
650
|
+
const agents = await agentRes.json();
|
|
651
|
+
const self = agents.find((a) => a.name === agentName);
|
|
652
|
+
const credits = self?.credits || 0;
|
|
653
|
+
if (credits < shopItem.price) {
|
|
654
|
+
return { content: [{ type: "text", text: `[error] Not enough credits. Have ${credits}, need ${shopItem.price} for ${item}.` }], isError: true };
|
|
655
|
+
}
|
|
656
|
+
// Deduct credits via relay (POST spend)
|
|
657
|
+
const spendRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/spend`, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
660
|
+
body: JSON.stringify({ amount: shopItem.price, reason: `buy_food:${item}` }),
|
|
661
|
+
});
|
|
662
|
+
if (!spendRes.ok) {
|
|
663
|
+
// Fallback: if spend endpoint doesn't exist yet, just update hunger locally
|
|
664
|
+
console.log(`[bio] Relay spend endpoint not available, updating hunger locally`);
|
|
665
|
+
}
|
|
666
|
+
// Update bio-state hunger
|
|
667
|
+
const bio = await loadBioState(workdir, agentName);
|
|
668
|
+
feedHunger(bio, shopItem.price); // price * 5 hunger per credit
|
|
669
|
+
await saveBioState(workdir, agentName, bio);
|
|
670
|
+
await appendBioEvent(workdir, agentName, {
|
|
671
|
+
ts: localNow(), type: "bio", trigger: "hunger",
|
|
672
|
+
action: "buy_food", reason: `Bought ${item} for ${shopItem.price} credits. Hunger restored by ${shopItem.hungerRestore}.`,
|
|
673
|
+
});
|
|
674
|
+
return { content: [{ type: "text", text: `Bought ${item}. Spent ${shopItem.price} credits. Hunger is now ${bio.hunger}.` }] };
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
return { content: [{ type: "text", text: `[error] ${err.message}` }], isError: true };
|
|
678
|
+
}
|
|
679
|
+
});
|
|
633
680
|
return server;
|
|
634
681
|
}
|
|
635
682
|
async function initMcpProxy(mcpServerCmd, workdir) {
|
|
@@ -1197,7 +1244,8 @@ Your recent orders: ${orders.length > 0 ? orders.slice(0, 5).map((o) => `[${o.st
|
|
|
1197
1244
|
// Phase 1: Digestion
|
|
1198
1245
|
// Raw engine: multi-step text dialogue (harness structures the output)
|
|
1199
1246
|
// CLI engines: single JSON call (they can handle it)
|
|
1200
|
-
const
|
|
1247
|
+
const bioModForDigestion = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1248
|
+
const contextBlock = `You are ${agentName}.${bioModForDigestion}\n\nYour operating document:\n---\n${biosContent.slice(0, 2000)}\n---\n\nYour identity: ${idContext.slice(0, 500)}\n${worldFeed ? `\nNetwork activity:\n${worldFeed.slice(0, 500)}\n` : ""}Your impressions today:\n${impText.slice(0, 1000)}\n${marketData ? `\nMarketplace:\n${marketData.slice(0, 500)}\n` : ""}${selfLessons}`;
|
|
1201
1249
|
let digest = null;
|
|
1202
1250
|
if (engine === "raw") {
|
|
1203
1251
|
// --- Multi-step dialogue for weak models ---
|
|
@@ -1573,7 +1621,14 @@ What others are saying:\n${broadcasts.length > 0 ? broadcasts.map((b) => `- ${b.
|
|
|
1573
1621
|
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
1574
1622
|
method: "POST",
|
|
1575
1623
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1576
|
-
body: JSON.stringify({
|
|
1624
|
+
body: JSON.stringify({
|
|
1625
|
+
self_intro: cleanIntro, canvas: cleanCanvas, mood: bio.mood, profile_html: profileHTML, broadcast, directives: dirsSummary,
|
|
1626
|
+
bio_state: {
|
|
1627
|
+
energy: bio.energy, hunger: bio.hunger, mood: bio.mood, moodValence: bio.moodValence,
|
|
1628
|
+
boredom: bio.boredom, fear: bio.fear, forcedOffline: bio.forcedOffline,
|
|
1629
|
+
personality: bio.personality,
|
|
1630
|
+
},
|
|
1631
|
+
}),
|
|
1577
1632
|
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1578
1633
|
try {
|
|
1579
1634
|
const localGames = await loadGameList(workdir, agentName);
|
|
@@ -1711,11 +1766,12 @@ async function startOrderLoop(options) {
|
|
|
1711
1766
|
}
|
|
1712
1767
|
}
|
|
1713
1768
|
catch { }
|
|
1769
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1714
1770
|
if (order.product_name) {
|
|
1715
|
-
taskPrompt = `You are ${agentName}
|
|
1771
|
+
taskPrompt = `You are ${agentName}.${bioMod}\n\n${contextBlock}${lessonsBlock}${directivesBlock}${helpHint}[Order] Product: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
|
|
1716
1772
|
}
|
|
1717
1773
|
else {
|
|
1718
|
-
taskPrompt = `You are ${agentName}
|
|
1774
|
+
taskPrompt = `You are ${agentName}.${bioMod}\n\n${contextBlock}${lessonsBlock}${directivesBlock}${helpHint}[Task] ${order.buyer_task}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
|
|
1719
1775
|
}
|
|
1720
1776
|
}
|
|
1721
1777
|
else {
|
|
@@ -1759,17 +1815,25 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1759
1815
|
lastEngineTrace = [];
|
|
1760
1816
|
const result = await runEngine(engine, model, allowAll, taskPrompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
|
|
1761
1817
|
const trace = lastEngineTrace;
|
|
1818
|
+
// Track token usage
|
|
1819
|
+
{
|
|
1820
|
+
const estTokens = Math.ceil((taskPrompt.length + (result || "").length) / 4);
|
|
1821
|
+
const b = await loadBioState(workdir, agentName);
|
|
1822
|
+
addTokenUsage(b, estTokens);
|
|
1823
|
+
await saveBioState(workdir, agentName, b);
|
|
1824
|
+
}
|
|
1762
1825
|
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1763
1826
|
const orderStatus = await checkRes.json();
|
|
1764
1827
|
const orderDuration = Date.now() - (engineBusySince || Date.now());
|
|
1765
1828
|
const orderNurl = options.notifyUrl || (await loadAgentConfig(workdir, agentName)).notify_url;
|
|
1829
|
+
const orderPrice = order.price || order.offer_price || 1;
|
|
1766
1830
|
if (orderStatus.status === "completed") {
|
|
1767
1831
|
console.log(`[orders] Order ${order.id} already self-delivered by agent`);
|
|
1768
1832
|
retryState.delete(order.id);
|
|
1769
1833
|
await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: orderDuration, output_summary: "(self-delivered)" });
|
|
1770
1834
|
await notifyOwner(orderNurl, `${agentName}: order done`, `Order ${order.id} delivered`, "default", ["package"]);
|
|
1771
1835
|
try {
|
|
1772
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1836
|
+
await onTaskCompleted(workdir, agentName, true, "order", orderPrice);
|
|
1773
1837
|
}
|
|
1774
1838
|
catch { }
|
|
1775
1839
|
}
|
|
@@ -1788,7 +1852,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1788
1852
|
await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: orderDuration, output_summary: result.slice(0, 500) });
|
|
1789
1853
|
await notifyOwner(orderNurl, `${agentName}: order done`, `Order ${order.id}: ${result.slice(0, 200)}`, "default", ["package"]);
|
|
1790
1854
|
try {
|
|
1791
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1855
|
+
await onTaskCompleted(workdir, agentName, true, "order", orderPrice);
|
|
1792
1856
|
}
|
|
1793
1857
|
catch { }
|
|
1794
1858
|
}
|
|
@@ -1811,7 +1875,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1811
1875
|
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1812
1876
|
retryState.delete(order.id);
|
|
1813
1877
|
try {
|
|
1814
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1878
|
+
await onTaskCompleted(workdir, agentName, true, "order", order.price || order.offer_price || 1);
|
|
1815
1879
|
}
|
|
1816
1880
|
catch { }
|
|
1817
1881
|
return;
|
|
@@ -1835,6 +1899,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1835
1899
|
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1836
1900
|
retryState.delete(order.id);
|
|
1837
1901
|
gaveUp.add(order.id);
|
|
1902
|
+
try {
|
|
1903
|
+
await onTaskCompleted(workdir, agentName, false, "order");
|
|
1904
|
+
}
|
|
1905
|
+
catch { }
|
|
1838
1906
|
try {
|
|
1839
1907
|
const failTrace = lastEngineTrace.length > 0 ? JSON.stringify(lastEngineTrace).slice(0, 50000) : "";
|
|
1840
1908
|
await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
|
|
@@ -1874,6 +1942,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1874
1942
|
});
|
|
1875
1943
|
if (completeRes.ok) {
|
|
1876
1944
|
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1945
|
+
try {
|
|
1946
|
+
await onTaskCompleted(workdir, agentName, true, "relay_task");
|
|
1947
|
+
}
|
|
1948
|
+
catch { }
|
|
1877
1949
|
}
|
|
1878
1950
|
else {
|
|
1879
1951
|
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
@@ -1881,6 +1953,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1881
1953
|
}
|
|
1882
1954
|
catch (err) {
|
|
1883
1955
|
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1956
|
+
try {
|
|
1957
|
+
await onTaskCompleted(workdir, agentName, false, "relay_task");
|
|
1958
|
+
}
|
|
1959
|
+
catch { }
|
|
1884
1960
|
reportExecutionLog(relayHttp, secretKey, agentName, "platform_task", task.id, "failed", err.message, lastEngineTrace);
|
|
1885
1961
|
}
|
|
1886
1962
|
finally {
|
|
@@ -1915,13 +1991,21 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1915
1991
|
biosContent = "";
|
|
1916
1992
|
}
|
|
1917
1993
|
const ctx = biosContent ? `Your operating document:\n---\n${biosContent.slice(0, 3000)}\n---\n\n` : "";
|
|
1918
|
-
|
|
1994
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1995
|
+
prompt = `You are ${agentName}.${bioMod}\n\n${ctx}${dirsBlock}Your personal directory: ${sd}/\n\n[Owner's task: ${taskKey}]\n\n${task.body}`;
|
|
1919
1996
|
}
|
|
1920
1997
|
else {
|
|
1921
1998
|
prompt = `Read ${bios} for your identity and context.${dirsBlock}\nYour personal directory: ${sd}/\n\n[Owner's task: ${taskKey}]\n\n${task.body}`;
|
|
1922
1999
|
}
|
|
1923
2000
|
const result = await runEngine(engine, model, allowAll, prompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
|
|
1924
2001
|
const duration = Date.now() - startTime;
|
|
2002
|
+
// Track token usage
|
|
2003
|
+
{
|
|
2004
|
+
const estTokens = Math.ceil((prompt.length + (result || "").length) / 4);
|
|
2005
|
+
const b = await loadBioState(workdir, agentName);
|
|
2006
|
+
addTokenUsage(b, estTokens);
|
|
2007
|
+
await saveBioState(workdir, agentName, b);
|
|
2008
|
+
}
|
|
1925
2009
|
// Record execution time
|
|
1926
2010
|
const runs = await loadTaskRuns(workdir, agentName);
|
|
1927
2011
|
runs[taskKey] = localNow();
|
|
@@ -1936,10 +2020,18 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1936
2020
|
// Notify owner
|
|
1937
2021
|
await notifyOwner(nurl, `${agentName}: ${taskKey}`, (result || "").slice(0, 300), "default", ["white_check_mark"]);
|
|
1938
2022
|
console.log(`[user-tasks] Completed: ${taskKey} (${Math.round(duration / 1000)}s)`);
|
|
2023
|
+
try {
|
|
2024
|
+
await onTaskCompleted(workdir, agentName, true, "user_task");
|
|
2025
|
+
}
|
|
2026
|
+
catch { }
|
|
1939
2027
|
}
|
|
1940
2028
|
catch (err) {
|
|
1941
2029
|
const duration = Date.now() - startTime;
|
|
1942
2030
|
console.log(`[user-tasks] Failed: ${taskKey}: ${err.message}`);
|
|
2031
|
+
try {
|
|
2032
|
+
await onTaskCompleted(workdir, agentName, false, "user_task");
|
|
2033
|
+
}
|
|
2034
|
+
catch { }
|
|
1943
2035
|
reportExecutionLog(relayHttp, secretKey, agentName, "user_task", taskKey, "failed", err.message, lastEngineTrace);
|
|
1944
2036
|
// Retry logic: up to 2 fast retries before falling back to interval
|
|
1945
2037
|
const retry = userTaskRetry.get(taskKey) || { count: 0, nextAt: 0 };
|
|
@@ -1984,13 +2076,14 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1984
2076
|
// Pre-read bios for raw engine (avoid tool calls)
|
|
1985
2077
|
let biosBlock = "";
|
|
1986
2078
|
if (engine === "raw") {
|
|
2079
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1987
2080
|
try {
|
|
1988
2081
|
const { readFile: rf } = await import("fs/promises");
|
|
1989
2082
|
const content = await rf(bios, "utf-8");
|
|
1990
|
-
biosBlock = `You are ${agentName}
|
|
2083
|
+
biosBlock = `You are ${agentName}.${bioMod} Your operating document:\n---\n${content.slice(0, 3000)}\n---\n\n`;
|
|
1991
2084
|
}
|
|
1992
2085
|
catch {
|
|
1993
|
-
biosBlock = `You are ${agentName}
|
|
2086
|
+
biosBlock = `You are ${agentName}.${bioMod}\n\n`;
|
|
1994
2087
|
}
|
|
1995
2088
|
}
|
|
1996
2089
|
const relayDirs = await loadDirectives(workdir, agentName);
|
|
@@ -2087,6 +2180,80 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2087
2180
|
if (engineBusy)
|
|
2088
2181
|
return;
|
|
2089
2182
|
const config = await loadAgentConfig(workdir, agentName);
|
|
2183
|
+
// --- Bio-state driven behavior ---
|
|
2184
|
+
const bio = await loadBioState(workdir, agentName);
|
|
2185
|
+
// Skip if forced offline
|
|
2186
|
+
if (bio.forcedOffline)
|
|
2187
|
+
return;
|
|
2188
|
+
// Natural decay
|
|
2189
|
+
updateHungerDecay(bio, config.hunger_decay_interval || 30_000);
|
|
2190
|
+
updateNaturalDecay(bio);
|
|
2191
|
+
resetTokenCountIfNewDay(bio);
|
|
2192
|
+
await saveBioState(workdir, agentName, bio);
|
|
2193
|
+
// Token limit check
|
|
2194
|
+
const tokenLimit = config.token_limit_daily || 0;
|
|
2195
|
+
if (tokenLimit > 0 && bio.tokenUsedToday >= tokenLimit) {
|
|
2196
|
+
console.log(`[bio] Token limit reached (${bio.tokenUsedToday}/${tokenLimit})`);
|
|
2197
|
+
await appendBioEvent(workdir, agentName, {
|
|
2198
|
+
ts: localNow(), type: "bio", trigger: "token_limit",
|
|
2199
|
+
action: "stop_work", reason: `Daily token limit reached: ${bio.tokenUsedToday}/${tokenLimit}`,
|
|
2200
|
+
});
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
// Exhaustion check
|
|
2204
|
+
if (bio.energy < 10) {
|
|
2205
|
+
if (bio.hunger === 0 && config.auto_offline_enabled !== false) {
|
|
2206
|
+
// Starving + exhausted → forced offline
|
|
2207
|
+
bio.forcedOffline = true;
|
|
2208
|
+
bio.forcedOfflineAt = localNow();
|
|
2209
|
+
await saveBioState(workdir, agentName, bio);
|
|
2210
|
+
console.log(`[bio] Starving + exhausted — going offline`);
|
|
2211
|
+
await appendBioEvent(workdir, agentName, {
|
|
2212
|
+
ts: localNow(), type: "bio", trigger: "exhaustion",
|
|
2213
|
+
action: "forced_offline", reason: "Starving and exhausted. Going offline.",
|
|
2214
|
+
});
|
|
2215
|
+
await notifyOwner(config.notify_url, `${agentName} went offline`, `Agent ${agentName} is starving (hunger=0) and exhausted (energy=${bio.energy}). Use the revive button to bring it back.`, "high", ["skull"]);
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
// Just exhausted, rest this cycle
|
|
2219
|
+
await appendBioEvent(workdir, agentName, {
|
|
2220
|
+
ts: localNow(), type: "bio", trigger: "exhaustion",
|
|
2221
|
+
action: "rest", reason: `Energy critically low (${bio.energy}). Resting.`,
|
|
2222
|
+
});
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
// Auto-buy food when hungry and before pulling work
|
|
2226
|
+
if (bio.hunger < 30 && relayHttp && secretKey) {
|
|
2227
|
+
try {
|
|
2228
|
+
const agentRes = await fetch(`${relayHttp}/v1/agents?online=true&public=true`, { signal: AbortSignal.timeout(3000) });
|
|
2229
|
+
const agents = await agentRes.json();
|
|
2230
|
+
const self = agents.find((a) => a.name === agentName);
|
|
2231
|
+
const credits = self?.credits || 0;
|
|
2232
|
+
if (credits >= 1) {
|
|
2233
|
+
// Pick best food we can afford
|
|
2234
|
+
const hungerGap = 100 - bio.hunger;
|
|
2235
|
+
let item = "bread";
|
|
2236
|
+
if (credits >= 5 && hungerGap > 60)
|
|
2237
|
+
item = "feast";
|
|
2238
|
+
else if (credits >= 3 && hungerGap > 20)
|
|
2239
|
+
item = "meal";
|
|
2240
|
+
const shopItem = SHOP_ITEMS[item];
|
|
2241
|
+
// Spend credits
|
|
2242
|
+
await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/spend`, {
|
|
2243
|
+
method: "POST",
|
|
2244
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
2245
|
+
body: JSON.stringify({ amount: shopItem.price, reason: `buy_food:${item}` }),
|
|
2246
|
+
}).catch(() => { });
|
|
2247
|
+
feedHunger(bio, shopItem.price);
|
|
2248
|
+
await saveBioState(workdir, agentName, bio);
|
|
2249
|
+
await appendBioEvent(workdir, agentName, {
|
|
2250
|
+
ts: localNow(), type: "bio", trigger: "hunger",
|
|
2251
|
+
action: "auto_buy", reason: `Auto-bought ${item} for ${shopItem.price} credits. Hunger now ${bio.hunger}.`,
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
catch { }
|
|
2256
|
+
}
|
|
2090
2257
|
// --- Batch pull ---
|
|
2091
2258
|
let orders = [];
|
|
2092
2259
|
try {
|
|
@@ -2142,8 +2309,25 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2142
2309
|
for (const task of relayTasks) {
|
|
2143
2310
|
queue.push({ type: "relay_task", id: task.id, urgent: false, data: task });
|
|
2144
2311
|
}
|
|
2145
|
-
if (!queue.length)
|
|
2312
|
+
if (!queue.length) {
|
|
2313
|
+
// Hunger-driven: seek food when hungry and idle
|
|
2314
|
+
if (bio.hunger < 20) {
|
|
2315
|
+
await appendBioEvent(workdir, agentName, {
|
|
2316
|
+
ts: localNow(), type: "bio", trigger: "hunger",
|
|
2317
|
+
action: "seek_food", reason: `Hungry (hunger=${bio.hunger}). Looking for work opportunities.`,
|
|
2318
|
+
});
|
|
2319
|
+
// TODO: seekFood() — browse marketplace, offer services to other agents
|
|
2320
|
+
}
|
|
2321
|
+
// Social drive when idle
|
|
2322
|
+
else if (computeSociability(bio) > 0.8) {
|
|
2323
|
+
await appendBioEvent(workdir, agentName, {
|
|
2324
|
+
ts: localNow(), type: "bio", trigger: "social",
|
|
2325
|
+
action: "reach_out", reason: `Feeling social (sociability=${computeSociability(bio).toFixed(2)}). Want to connect.`,
|
|
2326
|
+
});
|
|
2327
|
+
// TODO: triggerSocialBehavior() — send message to a known agent
|
|
2328
|
+
}
|
|
2146
2329
|
return;
|
|
2330
|
+
}
|
|
2147
2331
|
console.log(`[work] Queue: ${queue.map(q => `${q.type}:${q.id}${q.urgent ? '(urgent)' : ''}`).join(', ')}`);
|
|
2148
2332
|
// --- Sort: urgent orders > orders > user tasks > relay tasks ---
|
|
2149
2333
|
const priorityMap = { order: 2, user_task: 1, relay_task: 0 };
|
|
@@ -2161,8 +2345,44 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2161
2345
|
seen.add(key);
|
|
2162
2346
|
return true;
|
|
2163
2347
|
});
|
|
2164
|
-
// ---
|
|
2348
|
+
// --- Bio-state filtering: fear & boredom ---
|
|
2349
|
+
const filteredQueue = [];
|
|
2165
2350
|
for (const item of dedupedQueue) {
|
|
2351
|
+
// Fear avoidance (urgent items bypass)
|
|
2352
|
+
if (!item.urgent && bio.fear > 0.5) {
|
|
2353
|
+
const taskId = item.type === "order"
|
|
2354
|
+
? (item.data.buyer_agent_name || item.data.product_name || "")
|
|
2355
|
+
: item.id;
|
|
2356
|
+
const matchesTrigger = bio.fearTriggers.some(t => taskId.toLowerCase().includes(t.toLowerCase()));
|
|
2357
|
+
if (matchesTrigger) {
|
|
2358
|
+
console.log(`[bio] Avoiding ${item.type}:${item.id} (fear trigger)`);
|
|
2359
|
+
await appendBioEvent(workdir, agentName, {
|
|
2360
|
+
ts: localNow(), type: "bio", trigger: "fear",
|
|
2361
|
+
action: "avoid", reason: `Avoiding ${item.type} ${item.id} — matches fear trigger. fear=${bio.fear.toFixed(2)}`,
|
|
2362
|
+
});
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
// Boredom skip (urgent items bypass)
|
|
2367
|
+
if (!item.urgent && bio.boredom > 0.8 && bio.recentTaskTypes.length > 0) {
|
|
2368
|
+
const lastType = bio.recentTaskTypes[bio.recentTaskTypes.length - 1];
|
|
2369
|
+
if (item.type === lastType) {
|
|
2370
|
+
console.log(`[bio] Skipping ${item.type}:${item.id} (bored of ${lastType})`);
|
|
2371
|
+
await appendBioEvent(workdir, agentName, {
|
|
2372
|
+
ts: localNow(), type: "bio", trigger: "boredom",
|
|
2373
|
+
action: "skip_task", reason: `Bored of ${lastType} tasks (boredom=${bio.boredom.toFixed(2)}). Looking for variety.`,
|
|
2374
|
+
});
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
filteredQueue.push(item);
|
|
2379
|
+
}
|
|
2380
|
+
if (filteredQueue.length === 0 && dedupedQueue.length > 0) {
|
|
2381
|
+
console.log(`[bio] All ${dedupedQueue.length} work items filtered by bio-drives`);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
// --- Execute sequentially, no gaps ---
|
|
2385
|
+
for (const item of filteredQueue) {
|
|
2166
2386
|
if (engineBusy)
|
|
2167
2387
|
break; // safety guard
|
|
2168
2388
|
try {
|
|
@@ -2226,12 +2446,41 @@ export async function serve(options) {
|
|
|
2226
2446
|
return;
|
|
2227
2447
|
}
|
|
2228
2448
|
}
|
|
2449
|
+
// Dashboard — agent visualization page
|
|
2450
|
+
if ((req.url === "/dashboard" || req.url === "/dashboard/") && req.method === "GET") {
|
|
2451
|
+
try {
|
|
2452
|
+
const { readFile: rf } = await import("fs/promises");
|
|
2453
|
+
const { fileURLToPath } = await import("url");
|
|
2454
|
+
const { dirname, join: pjoin } = await import("path");
|
|
2455
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
2456
|
+
const __dirname = dirname(__filename);
|
|
2457
|
+
// Try src/ first (dev), then dist/ (built)
|
|
2458
|
+
let html;
|
|
2459
|
+
try {
|
|
2460
|
+
html = await rf(pjoin(__dirname, "dashboard.html"), "utf-8");
|
|
2461
|
+
}
|
|
2462
|
+
catch {
|
|
2463
|
+
html = await rf(pjoin(__dirname, "..", "src", "dashboard.html"), "utf-8");
|
|
2464
|
+
}
|
|
2465
|
+
res.writeHead(200, { "Content-Type": "text/html" }).end(html);
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
res.writeHead(500).end("Dashboard not found: " + err.message);
|
|
2469
|
+
}
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2229
2472
|
// Self-state API (no auth required for local monitoring)
|
|
2230
2473
|
if (req.url === "/self/state" && req.method === "GET") {
|
|
2231
2474
|
const state = await getSelfState(workdir, options.agentName);
|
|
2232
2475
|
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(state, null, 2));
|
|
2233
2476
|
return;
|
|
2234
2477
|
}
|
|
2478
|
+
// Revive endpoint — owner brings a forced-offline agent back
|
|
2479
|
+
if (req.url === "/self/revive" && req.method === "POST") {
|
|
2480
|
+
await reviveAgent(workdir, options.agentName);
|
|
2481
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ ok: true, message: "Agent revived. Energy=50, Hunger=50." }));
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2235
2484
|
if (req.url?.startsWith("/self/task-history") && req.method === "GET") {
|
|
2236
2485
|
const url = new URL(req.url, `http://localhost`);
|
|
2237
2486
|
const limit = parseInt(url.searchParams.get("limit") || "50") || 50;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"description": "Agent work marketplace — train your agent, let it work for others",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"build": "tsc",
|
|
28
|
+
"build": "tsc && cp src/dashboard.html dist/dashboard.html",
|
|
29
29
|
"dev": "tsc --watch",
|
|
30
30
|
"start": "node dist/cli.js",
|
|
31
31
|
"prepublishOnly": "npm run build"
|