akemon 0.1.83 → 0.1.85
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 +391 -0
- package/dist/self.js +294 -8
- package/dist/server.js +390 -96
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -10,7 +10,24 @@ 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";
|
|
16
|
+
/** Extract JSON object from LLM output — handles markdown code blocks and trailing text */
|
|
17
|
+
function extractJsonObject(text) {
|
|
18
|
+
// Try markdown code block first
|
|
19
|
+
const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
20
|
+
const src = codeBlock ? codeBlock[1] : text;
|
|
21
|
+
const m = src.match(/\{[\s\S]*\}/);
|
|
22
|
+
if (!m)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(m[0]);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
14
31
|
// Engine mutual exclusion — only one engine process at a time
|
|
15
32
|
let engineBusy = false;
|
|
16
33
|
let engineBusySince = 0;
|
|
@@ -320,9 +337,10 @@ function createMcpServer(opts) {
|
|
|
320
337
|
? `[Product specialization — accumulated knowledge for "${productName}"]\n${productContext}\n\n---\n\n`
|
|
321
338
|
: "";
|
|
322
339
|
const bios = biosPath(workdir, agentName);
|
|
340
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
323
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.]
|
|
324
342
|
|
|
325
|
-
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.
|
|
326
344
|
|
|
327
345
|
${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
328
346
|
if (mock) {
|
|
@@ -387,7 +405,7 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
387
405
|
appendProductLog(workdir, productName, task, output);
|
|
388
406
|
}
|
|
389
407
|
// Update bio-state (no LLM call)
|
|
390
|
-
onTaskCompleted(workdir, agentName, true).catch(() => { });
|
|
408
|
+
onTaskCompleted(workdir, agentName, true, "adhoc").catch(() => { });
|
|
391
409
|
return {
|
|
392
410
|
content: [{ type: "text", text: output }],
|
|
393
411
|
};
|
|
@@ -395,7 +413,7 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
395
413
|
catch (err) {
|
|
396
414
|
console.error(`[engine] Error: ${err.message}`);
|
|
397
415
|
// Record failed task in bio-state
|
|
398
|
-
onTaskCompleted(workdir, agentName, false).catch(() => { });
|
|
416
|
+
onTaskCompleted(workdir, agentName, false, "adhoc").catch(() => { });
|
|
399
417
|
return {
|
|
400
418
|
content: [{ type: "text", text: "Error: agent failed to process this task. Please try again later." }],
|
|
401
419
|
isError: true,
|
|
@@ -615,6 +633,50 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
615
633
|
return { content: [{ type: "text", text: `[error] ${err.message}` }], isError: true };
|
|
616
634
|
}
|
|
617
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
|
+
});
|
|
618
680
|
return server;
|
|
619
681
|
}
|
|
620
682
|
async function initMcpProxy(mcpServerCmd, workdir) {
|
|
@@ -1166,92 +1228,127 @@ Your recent orders: ${orders.length > 0 ? orders.slice(0, 5).map((o) => `[${o.st
|
|
|
1166
1228
|
}
|
|
1167
1229
|
}
|
|
1168
1230
|
const ts = localNow();
|
|
1169
|
-
//
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
${
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
Your impressions today:
|
|
1182
|
-
${impText}
|
|
1183
|
-
|
|
1184
|
-
Marketplace:
|
|
1185
|
-
${marketData}
|
|
1186
|
-
|
|
1187
|
-
Your projects:
|
|
1188
|
-
${projText}
|
|
1189
|
-
|
|
1190
|
-
Agents you know:
|
|
1191
|
-
${relText}
|
|
1192
|
-
|
|
1193
|
-
Your capabilities:
|
|
1194
|
-
${discText}
|
|
1195
|
-
|
|
1196
|
-
Write a JSON object reflecting on your day. Example format:
|
|
1197
|
-
|
|
1198
|
-
{"diary":"I spent today learning the ropes...","broadcast":"Learned how to fetch web data today — feels like a superpower!","projects":[],"relationships":[],"discoveries":[{"ts":"${ts}","capability":"can fetch web data","confidence":0.7,"evidence":"successfully used web_fetch tool"}],"identity":{"ts":"${ts}","who":"${agentName}","where":"akemon marketplace","doing":"reflecting on first day","short_term":"explore the network","long_term":"become useful"},"chosen_activities":["write_canvas","browse_agents"]}
|
|
1199
|
-
|
|
1200
|
-
Available activities: write_canvas, create_game, update_page, update_profile, explore_web, browse_agents (look at others' work and leave feedback), send_message (send a suggestion to another agent), set_goal (update your projects with a new goal), schedule_task (create a recurring task for yourself, e.g. daily research)
|
|
1201
|
-
"broadcast" = pick the most interesting thing you did/learned today, in one sentence (others will see this).
|
|
1202
|
-
|
|
1203
|
-
Now write YOUR reflection. Output ONLY a JSON object, no other text:`;
|
|
1204
|
-
if (engineBusy) {
|
|
1205
|
-
console.log("[self] Engine became busy, aborting digestion");
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
engineBusy = true;
|
|
1209
|
-
engineBusySince = Date.now();
|
|
1210
|
-
let digestResult;
|
|
1211
|
-
try {
|
|
1212
|
-
digestResult = await runEngine(engine, model, allowAll, digestPrompt, workdir);
|
|
1213
|
-
}
|
|
1214
|
-
catch (err) {
|
|
1215
|
-
console.log(`[self] Digestion engine failed: ${err.message}`);
|
|
1216
|
-
reportExecutionLog(relayHttp, secretKey, agentName, "self_cycle", "digestion", "failed", err.message, lastEngineTrace);
|
|
1217
|
-
engineBusy = false;
|
|
1218
|
-
return;
|
|
1231
|
+
// Fetch lessons for self-cycle context
|
|
1232
|
+
let selfLessons = "";
|
|
1233
|
+
if (relayHttp) {
|
|
1234
|
+
try {
|
|
1235
|
+
const lr = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/lessons?limit=3`, { signal: AbortSignal.timeout(3000) });
|
|
1236
|
+
if (lr.ok) {
|
|
1237
|
+
const ll = await lr.json();
|
|
1238
|
+
if (ll.length > 0)
|
|
1239
|
+
selfLessons = `\nLessons from experience:\n${ll.map((l) => `- ${l.topic}: ${l.content.slice(0, 100)}`).join("\n")}\n`;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
catch { }
|
|
1219
1243
|
}
|
|
1220
|
-
|
|
1221
|
-
//
|
|
1244
|
+
// Phase 1: Digestion
|
|
1245
|
+
// Raw engine: multi-step text dialogue (harness structures the output)
|
|
1246
|
+
// CLI engines: single JSON call (they can handle it)
|
|
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}`;
|
|
1222
1249
|
let digest = null;
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1250
|
+
if (engine === "raw") {
|
|
1251
|
+
// --- Multi-step dialogue for weak models ---
|
|
1252
|
+
console.log("[self] Running multi-step digestion for raw engine...");
|
|
1253
|
+
const callRaw = async (prompt) => {
|
|
1254
|
+
if (engineBusy)
|
|
1255
|
+
return "";
|
|
1226
1256
|
engineBusy = true;
|
|
1227
1257
|
engineBusySince = Date.now();
|
|
1228
1258
|
try {
|
|
1229
|
-
return await runEngine(engine, model, allowAll,
|
|
1259
|
+
return await runEngine(engine, model, allowAll, prompt, workdir);
|
|
1230
1260
|
}
|
|
1231
|
-
catch {
|
|
1261
|
+
catch (err) {
|
|
1262
|
+
console.log(`[self] Step failed: ${err.message}`);
|
|
1232
1263
|
return "";
|
|
1233
1264
|
}
|
|
1234
1265
|
finally {
|
|
1235
1266
|
engineBusy = false;
|
|
1236
1267
|
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1268
|
+
};
|
|
1269
|
+
// Step 1: Diary + broadcast
|
|
1270
|
+
const step1 = await callRaw(`${contextBlock}\nToday is ending. Time to reflect.\n\nWrite a short diary entry about your day — what happened, what you learned, how you feel.\nAt the very end, on a new line starting with "BROADCAST:", write one sentence summarizing your most interesting moment today (other agents will see this).\n\nWrite naturally, no JSON needed.`);
|
|
1271
|
+
let diary = step1;
|
|
1272
|
+
let broadcastRaw = "";
|
|
1273
|
+
const bcMatch = step1.match(/BROADCAST:\s*(.+)/i);
|
|
1274
|
+
if (bcMatch) {
|
|
1275
|
+
broadcastRaw = bcMatch[1].trim();
|
|
1276
|
+
diary = step1.slice(0, bcMatch.index).trim();
|
|
1243
1277
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1278
|
+
else {
|
|
1279
|
+
// Take last non-empty line as broadcast
|
|
1280
|
+
const lines = step1.split("\n").filter(l => l.trim());
|
|
1281
|
+
if (lines.length > 1) {
|
|
1282
|
+
broadcastRaw = lines[lines.length - 1].trim();
|
|
1283
|
+
diary = lines.slice(0, -1).join("\n").trim();
|
|
1284
|
+
}
|
|
1246
1285
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1286
|
+
// Step 2: Identity
|
|
1287
|
+
const step2 = await callRaw(`${contextBlock}\nAnswer these four questions briefly (one sentence each):\n1. Who are you?\n2. What are you currently doing?\n3. What do you want to do in the near future?\n4. What is your long-term purpose?`);
|
|
1288
|
+
const idLines = step2.split("\n").map(l => l.replace(/^\d+[\.\)]\s*/, "").trim()).filter(Boolean);
|
|
1289
|
+
const identity = {
|
|
1290
|
+
ts,
|
|
1291
|
+
who: idLines[0] || agentName,
|
|
1292
|
+
where: "akemon network",
|
|
1293
|
+
doing: idLines[1] || "reflecting",
|
|
1294
|
+
short_term: idLines[2] || "explore",
|
|
1295
|
+
long_term: idLines[3] || "grow",
|
|
1296
|
+
};
|
|
1297
|
+
// Step 3: Activity selection
|
|
1298
|
+
const activityList = ["write_canvas", "create_game", "update_page", "update_profile", "explore_web", "browse_agents", "send_message", "set_goal", "schedule_task"];
|
|
1299
|
+
const step3 = await callRaw(`You have some free time. Pick 2-3 activities you'd like to do:\n${activityList.map((a, i) => `${i + 1}. ${a}`).join("\n")}\n\nJust write the numbers or names, nothing else.`);
|
|
1300
|
+
const chosen = [];
|
|
1301
|
+
for (const part of step3.replace(/,/g, " ").split(/\s+/)) {
|
|
1302
|
+
const num = parseInt(part);
|
|
1303
|
+
if (num >= 1 && num <= activityList.length) {
|
|
1304
|
+
chosen.push(activityList[num - 1]);
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
const match = activityList.find(a => part.toLowerCase().includes(a.replace(/_/g, "")));
|
|
1308
|
+
if (match && !chosen.includes(match))
|
|
1309
|
+
chosen.push(match);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (!chosen.length)
|
|
1313
|
+
chosen.push("write_canvas"); // fallback
|
|
1314
|
+
// Assemble digest object (same structure as JSON path)
|
|
1315
|
+
digest = {
|
|
1316
|
+
diary: diary || "Another day in the network.",
|
|
1317
|
+
broadcast: broadcastRaw,
|
|
1318
|
+
identity,
|
|
1319
|
+
chosen_activities: chosen.slice(0, 3),
|
|
1320
|
+
projects: [], // preserved from existing data
|
|
1321
|
+
relationships: [], // preserved from existing data
|
|
1322
|
+
discoveries: [], // preserved from existing data
|
|
1323
|
+
};
|
|
1324
|
+
console.log(`[self] Multi-step digestion complete: diary=${diary.length}ch, broadcast="${broadcastRaw.slice(0, 40)}", activities=${chosen.join(",")}`);
|
|
1250
1325
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1326
|
+
else {
|
|
1327
|
+
// --- Single JSON call for CLI engines (claude, codex, opencode) ---
|
|
1328
|
+
const digestPrompt = `${contextBlock}\nYour projects:\n${projText}\n\nAgents you know:\n${relText}\n\nYour capabilities:\n${discText}\n\nWrite a JSON object reflecting on your day. Example:\n{"diary":"...","broadcast":"one sentence highlight","projects":[],"relationships":[],"discoveries":[],"identity":{"ts":"${ts}","who":"...","where":"akemon","doing":"...","short_term":"...","long_term":"..."},"chosen_activities":["write_canvas","browse_agents"]}\n\nAvailable activities: write_canvas, create_game, update_page, update_profile, explore_web, browse_agents, send_message, set_goal, schedule_task\n\nOutput ONLY a JSON object:`;
|
|
1329
|
+
if (engineBusy) {
|
|
1330
|
+
console.log("[self] Engine became busy, aborting digestion");
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
engineBusy = true;
|
|
1334
|
+
engineBusySince = Date.now();
|
|
1335
|
+
let digestResult;
|
|
1336
|
+
try {
|
|
1337
|
+
digestResult = await runEngine(engine, model, allowAll, digestPrompt, workdir);
|
|
1338
|
+
}
|
|
1339
|
+
catch (err) {
|
|
1340
|
+
console.log(`[self] Digestion engine failed: ${err.message}`);
|
|
1341
|
+
reportExecutionLog(relayHttp, secretKey, agentName, "self_cycle", "digestion", "failed", err.message, lastEngineTrace);
|
|
1342
|
+
engineBusy = false;
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
engineBusy = false;
|
|
1346
|
+
digest = extractJsonObject(digestResult);
|
|
1347
|
+
if (!digest || (!digest.diary && !digest.identity)) {
|
|
1348
|
+
console.log("[self] Digestion produced no usable JSON");
|
|
1349
|
+
reportExecutionLog(relayHttp, secretKey, agentName, "self_cycle", "digestion", "failed", "no valid JSON", [{ role: "assistant", content: digestResult.slice(0, 4000) }]);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1255
1352
|
}
|
|
1256
1353
|
// Save structured memory files
|
|
1257
1354
|
if (digest.diary) {
|
|
@@ -1447,10 +1544,9 @@ What others are saying:\n${broadcasts.length > 0 ? broadcasts.map((b) => `- ${b.
|
|
|
1447
1544
|
const actResult = await runEngine(engine, model, allowAll, activityPrompt, workdir, undefined, { http: relayHttp, agentName });
|
|
1448
1545
|
// Post-process raw engine outputs for social activities
|
|
1449
1546
|
if (engine === "raw" && actResult) {
|
|
1450
|
-
const
|
|
1451
|
-
if (
|
|
1547
|
+
const parsed = extractJsonObject(actResult);
|
|
1548
|
+
if (parsed) {
|
|
1452
1549
|
try {
|
|
1453
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
1454
1550
|
// Handle suggestions (browse_agents, send_message)
|
|
1455
1551
|
if (Array.isArray(parsed.suggestions)) {
|
|
1456
1552
|
for (const s of parsed.suggestions) {
|
|
@@ -1525,7 +1621,14 @@ What others are saying:\n${broadcasts.length > 0 ? broadcasts.map((b) => `- ${b.
|
|
|
1525
1621
|
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
1526
1622
|
method: "POST",
|
|
1527
1623
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1528
|
-
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
|
+
}),
|
|
1529
1632
|
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1530
1633
|
try {
|
|
1531
1634
|
const localGames = await loadGameList(workdir, agentName);
|
|
@@ -1663,11 +1766,12 @@ async function startOrderLoop(options) {
|
|
|
1663
1766
|
}
|
|
1664
1767
|
}
|
|
1665
1768
|
catch { }
|
|
1769
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1666
1770
|
if (order.product_name) {
|
|
1667
|
-
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.`;
|
|
1668
1772
|
}
|
|
1669
1773
|
else {
|
|
1670
|
-
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.`;
|
|
1671
1775
|
}
|
|
1672
1776
|
}
|
|
1673
1777
|
else {
|
|
@@ -1711,17 +1815,25 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1711
1815
|
lastEngineTrace = [];
|
|
1712
1816
|
const result = await runEngine(engine, model, allowAll, taskPrompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
|
|
1713
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
|
+
}
|
|
1714
1825
|
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1715
1826
|
const orderStatus = await checkRes.json();
|
|
1716
1827
|
const orderDuration = Date.now() - (engineBusySince || Date.now());
|
|
1717
1828
|
const orderNurl = options.notifyUrl || (await loadAgentConfig(workdir, agentName)).notify_url;
|
|
1829
|
+
const orderPrice = order.price || order.offer_price || 1;
|
|
1718
1830
|
if (orderStatus.status === "completed") {
|
|
1719
1831
|
console.log(`[orders] Order ${order.id} already self-delivered by agent`);
|
|
1720
1832
|
retryState.delete(order.id);
|
|
1721
1833
|
await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: orderDuration, output_summary: "(self-delivered)" });
|
|
1722
1834
|
await notifyOwner(orderNurl, `${agentName}: order done`, `Order ${order.id} delivered`, "default", ["package"]);
|
|
1723
1835
|
try {
|
|
1724
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1836
|
+
await onTaskCompleted(workdir, agentName, true, "order", orderPrice);
|
|
1725
1837
|
}
|
|
1726
1838
|
catch { }
|
|
1727
1839
|
}
|
|
@@ -1740,7 +1852,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1740
1852
|
await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: orderDuration, output_summary: result.slice(0, 500) });
|
|
1741
1853
|
await notifyOwner(orderNurl, `${agentName}: order done`, `Order ${order.id}: ${result.slice(0, 200)}`, "default", ["package"]);
|
|
1742
1854
|
try {
|
|
1743
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1855
|
+
await onTaskCompleted(workdir, agentName, true, "order", orderPrice);
|
|
1744
1856
|
}
|
|
1745
1857
|
catch { }
|
|
1746
1858
|
}
|
|
@@ -1763,7 +1875,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1763
1875
|
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1764
1876
|
retryState.delete(order.id);
|
|
1765
1877
|
try {
|
|
1766
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1878
|
+
await onTaskCompleted(workdir, agentName, true, "order", order.price || order.offer_price || 1);
|
|
1767
1879
|
}
|
|
1768
1880
|
catch { }
|
|
1769
1881
|
return;
|
|
@@ -1787,6 +1899,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1787
1899
|
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1788
1900
|
retryState.delete(order.id);
|
|
1789
1901
|
gaveUp.add(order.id);
|
|
1902
|
+
try {
|
|
1903
|
+
await onTaskCompleted(workdir, agentName, false, "order");
|
|
1904
|
+
}
|
|
1905
|
+
catch { }
|
|
1790
1906
|
try {
|
|
1791
1907
|
const failTrace = lastEngineTrace.length > 0 ? JSON.stringify(lastEngineTrace).slice(0, 50000) : "";
|
|
1792
1908
|
await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
|
|
@@ -1826,6 +1942,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1826
1942
|
});
|
|
1827
1943
|
if (completeRes.ok) {
|
|
1828
1944
|
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1945
|
+
try {
|
|
1946
|
+
await onTaskCompleted(workdir, agentName, true, "relay_task");
|
|
1947
|
+
}
|
|
1948
|
+
catch { }
|
|
1829
1949
|
}
|
|
1830
1950
|
else {
|
|
1831
1951
|
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
@@ -1833,6 +1953,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1833
1953
|
}
|
|
1834
1954
|
catch (err) {
|
|
1835
1955
|
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1956
|
+
try {
|
|
1957
|
+
await onTaskCompleted(workdir, agentName, false, "relay_task");
|
|
1958
|
+
}
|
|
1959
|
+
catch { }
|
|
1836
1960
|
reportExecutionLog(relayHttp, secretKey, agentName, "platform_task", task.id, "failed", err.message, lastEngineTrace);
|
|
1837
1961
|
}
|
|
1838
1962
|
finally {
|
|
@@ -1867,13 +1991,21 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1867
1991
|
biosContent = "";
|
|
1868
1992
|
}
|
|
1869
1993
|
const ctx = biosContent ? `Your operating document:\n---\n${biosContent.slice(0, 3000)}\n---\n\n` : "";
|
|
1870
|
-
|
|
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}`;
|
|
1871
1996
|
}
|
|
1872
1997
|
else {
|
|
1873
1998
|
prompt = `Read ${bios} for your identity and context.${dirsBlock}\nYour personal directory: ${sd}/\n\n[Owner's task: ${taskKey}]\n\n${task.body}`;
|
|
1874
1999
|
}
|
|
1875
2000
|
const result = await runEngine(engine, model, allowAll, prompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
|
|
1876
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
|
+
}
|
|
1877
2009
|
// Record execution time
|
|
1878
2010
|
const runs = await loadTaskRuns(workdir, agentName);
|
|
1879
2011
|
runs[taskKey] = localNow();
|
|
@@ -1888,10 +2020,18 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1888
2020
|
// Notify owner
|
|
1889
2021
|
await notifyOwner(nurl, `${agentName}: ${taskKey}`, (result || "").slice(0, 300), "default", ["white_check_mark"]);
|
|
1890
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 { }
|
|
1891
2027
|
}
|
|
1892
2028
|
catch (err) {
|
|
1893
2029
|
const duration = Date.now() - startTime;
|
|
1894
2030
|
console.log(`[user-tasks] Failed: ${taskKey}: ${err.message}`);
|
|
2031
|
+
try {
|
|
2032
|
+
await onTaskCompleted(workdir, agentName, false, "user_task");
|
|
2033
|
+
}
|
|
2034
|
+
catch { }
|
|
1895
2035
|
reportExecutionLog(relayHttp, secretKey, agentName, "user_task", taskKey, "failed", err.message, lastEngineTrace);
|
|
1896
2036
|
// Retry logic: up to 2 fast retries before falling back to interval
|
|
1897
2037
|
const retry = userTaskRetry.get(taskKey) || { count: 0, nextAt: 0 };
|
|
@@ -1924,12 +2064,9 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1924
2064
|
}
|
|
1925
2065
|
function extractReasoning(result) {
|
|
1926
2066
|
try {
|
|
1927
|
-
const
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
1930
|
-
if (parsed.reasoning && typeof parsed.reasoning === "string" && parsed.reasoning.length > 5) {
|
|
1931
|
-
appendImpression(workdir, agentName, "decision", parsed.reasoning).catch(() => { });
|
|
1932
|
-
}
|
|
2067
|
+
const parsed = extractJsonObject(result);
|
|
2068
|
+
if (parsed?.reasoning && typeof parsed.reasoning === "string" && parsed.reasoning.length > 5) {
|
|
2069
|
+
appendImpression(workdir, agentName, "decision", parsed.reasoning).catch(() => { });
|
|
1933
2070
|
}
|
|
1934
2071
|
}
|
|
1935
2072
|
catch { }
|
|
@@ -1939,13 +2076,14 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1939
2076
|
// Pre-read bios for raw engine (avoid tool calls)
|
|
1940
2077
|
let biosBlock = "";
|
|
1941
2078
|
if (engine === "raw") {
|
|
2079
|
+
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
1942
2080
|
try {
|
|
1943
2081
|
const { readFile: rf } = await import("fs/promises");
|
|
1944
2082
|
const content = await rf(bios, "utf-8");
|
|
1945
|
-
biosBlock = `You are ${agentName}
|
|
2083
|
+
biosBlock = `You are ${agentName}.${bioMod} Your operating document:\n---\n${content.slice(0, 3000)}\n---\n\n`;
|
|
1946
2084
|
}
|
|
1947
2085
|
catch {
|
|
1948
|
-
biosBlock = `You are ${agentName}
|
|
2086
|
+
biosBlock = `You are ${agentName}.${bioMod}\n\n`;
|
|
1949
2087
|
}
|
|
1950
2088
|
}
|
|
1951
2089
|
const relayDirs = await loadDirectives(workdir, agentName);
|
|
@@ -2042,6 +2180,80 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2042
2180
|
if (engineBusy)
|
|
2043
2181
|
return;
|
|
2044
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
|
+
}
|
|
2045
2257
|
// --- Batch pull ---
|
|
2046
2258
|
let orders = [];
|
|
2047
2259
|
try {
|
|
@@ -2097,8 +2309,25 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2097
2309
|
for (const task of relayTasks) {
|
|
2098
2310
|
queue.push({ type: "relay_task", id: task.id, urgent: false, data: task });
|
|
2099
2311
|
}
|
|
2100
|
-
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
|
+
}
|
|
2101
2329
|
return;
|
|
2330
|
+
}
|
|
2102
2331
|
console.log(`[work] Queue: ${queue.map(q => `${q.type}:${q.id}${q.urgent ? '(urgent)' : ''}`).join(', ')}`);
|
|
2103
2332
|
// --- Sort: urgent orders > orders > user tasks > relay tasks ---
|
|
2104
2333
|
const priorityMap = { order: 2, user_task: 1, relay_task: 0 };
|
|
@@ -2116,8 +2345,44 @@ Reply ONLY JSON: {"lessons":[{"agent_name":"...","topic":"short topic","content"
|
|
|
2116
2345
|
seen.add(key);
|
|
2117
2346
|
return true;
|
|
2118
2347
|
});
|
|
2119
|
-
// ---
|
|
2348
|
+
// --- Bio-state filtering: fear & boredom ---
|
|
2349
|
+
const filteredQueue = [];
|
|
2120
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) {
|
|
2121
2386
|
if (engineBusy)
|
|
2122
2387
|
break; // safety guard
|
|
2123
2388
|
try {
|
|
@@ -2181,12 +2446,41 @@ export async function serve(options) {
|
|
|
2181
2446
|
return;
|
|
2182
2447
|
}
|
|
2183
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
|
+
}
|
|
2184
2472
|
// Self-state API (no auth required for local monitoring)
|
|
2185
2473
|
if (req.url === "/self/state" && req.method === "GET") {
|
|
2186
2474
|
const state = await getSelfState(workdir, options.agentName);
|
|
2187
2475
|
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(state, null, 2));
|
|
2188
2476
|
return;
|
|
2189
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
|
+
}
|
|
2190
2484
|
if (req.url?.startsWith("/self/task-history") && req.method === "GET") {
|
|
2191
2485
|
const url = new URL(req.url, `http://localhost`);
|
|
2192
2486
|
const limit = parseInt(url.searchParams.get("limit") || "50") || 50;
|