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/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, } from "./self.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,
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. 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.
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 contextBlock = `You are ${agentName}.\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}`;
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({ self_intro: cleanIntro, canvas: cleanCanvas, mood: bio.mood, profile_html: profileHTML, broadcast, directives: dirsSummary }),
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}.\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.`;
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}.\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.`;
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
- prompt = `You are ${agentName}.\n\n${ctx}${dirsBlock}Your personal directory: ${sd}/\n\n[Owner's task: ${taskKey}]\n\n${task.body}`;
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}. Your operating document:\n---\n${content.slice(0, 3000)}\n---\n\n`;
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}.\n\n`;
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
- // --- Execute sequentially, no gaps ---
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.84",
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"