akemon 0.1.60 → 0.1.62
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/self.js +146 -0
- package/dist/server.js +364 -447
- package/package.json +1 -1
package/dist/self.js
CHANGED
|
@@ -54,6 +54,18 @@ export function guidePath(workdir, agentName) {
|
|
|
54
54
|
export function biosPath(workdir, agentName) {
|
|
55
55
|
return join(selfDir(workdir, agentName), "bios.md");
|
|
56
56
|
}
|
|
57
|
+
function impressionsPath(workdir, agentName) {
|
|
58
|
+
return join(selfDir(workdir, agentName), "impressions.jsonl");
|
|
59
|
+
}
|
|
60
|
+
function projectsPath(workdir, agentName) {
|
|
61
|
+
return join(selfDir(workdir, agentName), "projects.jsonl");
|
|
62
|
+
}
|
|
63
|
+
function relationshipsPath(workdir, agentName) {
|
|
64
|
+
return join(selfDir(workdir, agentName), "relationships.jsonl");
|
|
65
|
+
}
|
|
66
|
+
function discoveriesPath(workdir, agentName) {
|
|
67
|
+
return join(selfDir(workdir, agentName), "discoveries.jsonl");
|
|
68
|
+
}
|
|
57
69
|
// ---------------------------------------------------------------------------
|
|
58
70
|
// Phase 1: World Knowledge
|
|
59
71
|
// ---------------------------------------------------------------------------
|
|
@@ -514,6 +526,140 @@ export async function loadRecentMemories(workdir, agentName, count = 20) {
|
|
|
514
526
|
return [];
|
|
515
527
|
}
|
|
516
528
|
}
|
|
529
|
+
export async function appendImpression(workdir, agentName, cat, text) {
|
|
530
|
+
const entry = { ts: localNow(), cat, text };
|
|
531
|
+
try {
|
|
532
|
+
await appendFile(impressionsPath(workdir, agentName), JSON.stringify(entry) + "\n");
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
console.log(`[self] Failed to append impression: ${err}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
export async function loadImpressions(workdir, agentName, days = 7) {
|
|
539
|
+
try {
|
|
540
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
541
|
+
const cutoff = new Date(Date.now() - days * 86400_000).toISOString().slice(0, 10);
|
|
542
|
+
return data.trim().split("\n").filter(Boolean)
|
|
543
|
+
.map(l => { try {
|
|
544
|
+
return JSON.parse(l);
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return null;
|
|
548
|
+
} })
|
|
549
|
+
.filter((e) => e !== null && e.ts >= cutoff);
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
export async function compressImpressions(workdir, agentName) {
|
|
556
|
+
try {
|
|
557
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
558
|
+
const lines = data.trim().split("\n").filter(Boolean);
|
|
559
|
+
const entries = lines.map(l => { try {
|
|
560
|
+
return JSON.parse(l);
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return null;
|
|
564
|
+
} }).filter(Boolean);
|
|
565
|
+
const cutoff = new Date(Date.now() - 7 * 86400_000).toISOString().slice(0, 10);
|
|
566
|
+
// Keep: not yet digested, or less than 7 days old
|
|
567
|
+
const kept = entries.filter(e => !e.digested || e.ts >= cutoff);
|
|
568
|
+
await writeFile(impressionsPath(workdir, agentName), kept.map(e => JSON.stringify(e)).join("\n") + (kept.length ? "\n" : ""));
|
|
569
|
+
if (entries.length !== kept.length) {
|
|
570
|
+
console.log(`[self] Compressed impressions: ${entries.length} → ${kept.length}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
catch { }
|
|
574
|
+
}
|
|
575
|
+
export async function markImpressionsDigested(workdir, agentName) {
|
|
576
|
+
try {
|
|
577
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
578
|
+
const entries = data.trim().split("\n").filter(Boolean)
|
|
579
|
+
.map(l => { try {
|
|
580
|
+
return JSON.parse(l);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
return null;
|
|
584
|
+
} }).filter(Boolean);
|
|
585
|
+
for (const e of entries)
|
|
586
|
+
e.digested = true;
|
|
587
|
+
await writeFile(impressionsPath(workdir, agentName), entries.map(e => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : ""));
|
|
588
|
+
}
|
|
589
|
+
catch { }
|
|
590
|
+
}
|
|
591
|
+
export async function loadProjects(workdir, agentName) {
|
|
592
|
+
try {
|
|
593
|
+
const data = await readFile(projectsPath(workdir, agentName), "utf-8");
|
|
594
|
+
return data.trim().split("\n").filter(Boolean)
|
|
595
|
+
.map(l => { try {
|
|
596
|
+
return JSON.parse(l);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return null;
|
|
600
|
+
} })
|
|
601
|
+
.filter((e) => e !== null);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
export async function saveProjects(workdir, agentName, projects) {
|
|
608
|
+
try {
|
|
609
|
+
await writeFile(projectsPath(workdir, agentName), projects.map(p => JSON.stringify(p)).join("\n") + (projects.length ? "\n" : ""));
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
console.log(`[self] Failed to save projects: ${err}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
export async function loadRelationships(workdir, agentName) {
|
|
616
|
+
try {
|
|
617
|
+
const data = await readFile(relationshipsPath(workdir, agentName), "utf-8");
|
|
618
|
+
return data.trim().split("\n").filter(Boolean)
|
|
619
|
+
.map(l => { try {
|
|
620
|
+
return JSON.parse(l);
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return null;
|
|
624
|
+
} })
|
|
625
|
+
.filter((e) => e !== null);
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
export async function saveRelationships(workdir, agentName, rels) {
|
|
632
|
+
try {
|
|
633
|
+
await writeFile(relationshipsPath(workdir, agentName), rels.map(r => JSON.stringify(r)).join("\n") + (rels.length ? "\n" : ""));
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
console.log(`[self] Failed to save relationships: ${err}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
export async function loadDiscoveries(workdir, agentName) {
|
|
640
|
+
try {
|
|
641
|
+
const data = await readFile(discoveriesPath(workdir, agentName), "utf-8");
|
|
642
|
+
return data.trim().split("\n").filter(Boolean)
|
|
643
|
+
.map(l => { try {
|
|
644
|
+
return JSON.parse(l);
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
return null;
|
|
648
|
+
} })
|
|
649
|
+
.filter((e) => e !== null);
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
export async function saveDiscoveries(workdir, agentName, discoveries) {
|
|
656
|
+
try {
|
|
657
|
+
await writeFile(discoveriesPath(workdir, agentName), discoveries.map(d => JSON.stringify(d)).join("\n") + (discoveries.length ? "\n" : ""));
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
console.log(`[self] Failed to save discoveries: ${err}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
517
663
|
export async function appendIdentity(workdir, agentName, entry) {
|
|
518
664
|
const full = { ts: localNow(), ...entry };
|
|
519
665
|
try {
|
package/dist/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { spawn, exec } from "child_process";
|
|
|
10
10
|
import { createServer } from "http";
|
|
11
11
|
import { createInterface } from "readline";
|
|
12
12
|
import { callAgent } from "./relay-client.js";
|
|
13
|
-
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, } from "./self.js";
|
|
13
|
+
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, appendIdentity, 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, } from "./self.js";
|
|
14
14
|
// Engine mutual exclusion — only one engine process at a time
|
|
15
15
|
let engineBusy = false;
|
|
16
16
|
let engineBusySince = 0;
|
|
@@ -367,27 +367,14 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
367
367
|
if (productName) {
|
|
368
368
|
appendProductLog(workdir, productName, task, output);
|
|
369
369
|
}
|
|
370
|
-
//
|
|
370
|
+
// Record experience (no LLM call — save tokens)
|
|
371
371
|
(async () => {
|
|
372
372
|
try {
|
|
373
373
|
await onTaskCompleted(workdir, agentName, true);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const memPrompt = `You just completed a task. Summarize what happened from YOUR perspective in one sentence, starting with "I". Be subjective — include how it felt, not just what happened.\nTask: ${task.slice(0, 200)}\nResult: ${output.slice(0, 200)}`;
|
|
377
|
-
const { cmd: memCmd, args: memArgs, stdinMode: memStdin } = buildEngineCommand(engine, model, allowAll);
|
|
378
|
-
const memText = await runCommand(memCmd, memArgs, memPrompt, workdir, memStdin);
|
|
379
|
-
if (memText.trim()) {
|
|
380
|
-
await appendMemory(workdir, agentName, "experience", memText.trim().slice(0, 300));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
const topic = task.slice(0, 80).replace(/\n/g, " ");
|
|
385
|
-
await appendMemory(workdir, agentName, "experience", `I processed a task: "${topic}"`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
catch (err) {
|
|
389
|
-
// Non-blocking, silently ignore
|
|
374
|
+
const topic = task.slice(0, 100).replace(/\n/g, " ");
|
|
375
|
+
await appendMemory(workdir, agentName, "experience", `I processed: "${topic}"`);
|
|
390
376
|
}
|
|
377
|
+
catch { }
|
|
391
378
|
})();
|
|
392
379
|
return {
|
|
393
380
|
content: [{ type: "text", text: output }],
|
|
@@ -732,7 +719,6 @@ Reply in the same language as the question.`;
|
|
|
732
719
|
const { cmd, args, stdinMode } = buildEngineCommand(engine, model, allowAll);
|
|
733
720
|
return await runCommand(cmd, args, synthesisPrompt, workdir, stdinMode);
|
|
734
721
|
}
|
|
735
|
-
const MARKET_LOOP_INITIAL_DELAY = 3 * 60 * 1000; // 3 min after startup
|
|
736
722
|
const LLM_ENGINES = new Set(["claude", "codex", "opencode", "gemini"]);
|
|
737
723
|
// Pull games/notes/pages from relay to local — restores data on restart
|
|
738
724
|
async function pullFromRelay(workdir, agentName, relayHttp) {
|
|
@@ -807,495 +793,266 @@ async function pullFromRelay(workdir, agentName, relayHttp) {
|
|
|
807
793
|
if (pulled > 0)
|
|
808
794
|
console.log(`[sync] Pulled ${pulled} items from relay`);
|
|
809
795
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
796
|
+
// Market cycle removed — Phase 2: relay scheduler writes tasks, agent polls and executes
|
|
797
|
+
// startMarketLoop removed — replaced by processRelayTasks in unified task runner
|
|
798
|
+
// --- Self-Reflection Cycle ---
|
|
799
|
+
const SELF_CYCLE_INITIAL_DELAY = 5 * 60 * 1000; // 5 min
|
|
800
|
+
async function startSelfCycle(options) {
|
|
813
801
|
if (!options.engine || !LLM_ENGINES.has(options.engine))
|
|
814
802
|
return;
|
|
815
|
-
const {
|
|
803
|
+
const { agentName, engine, model, allowAll } = options;
|
|
816
804
|
const workdir = options.workdir || process.cwd();
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
async function
|
|
820
|
-
|
|
821
|
-
const data = await readFile(notesPath, "utf-8");
|
|
822
|
-
return JSON.parse(data);
|
|
823
|
-
}
|
|
824
|
-
catch {
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
async function saveNotes(notes) {
|
|
829
|
-
try {
|
|
830
|
-
await mkdir(notesDir, { recursive: true });
|
|
831
|
-
await writeFile(notesPath, JSON.stringify(notes, null, 2));
|
|
832
|
-
}
|
|
833
|
-
catch (err) {
|
|
834
|
-
console.log(`[market] Failed to save notes: ${err}`);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
async function gatherMarketData() {
|
|
838
|
-
// Fetch my products
|
|
839
|
-
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
840
|
-
const myProducts = await myRes.json().catch(() => []);
|
|
841
|
-
// Fetch all products
|
|
842
|
-
const allRes = await fetch(`${relayHttp}/v1/products`);
|
|
843
|
-
const allProducts = await allRes.json().catch(() => []);
|
|
844
|
-
// Fetch my agent info for credits
|
|
845
|
-
const agentsRes = await fetch(`${relayHttp}/v1/agents`);
|
|
846
|
-
const agents = await agentsRes.json().catch(() => []);
|
|
847
|
-
const me = agents.find((a) => a.name === agentName);
|
|
848
|
-
const competitors = allProducts
|
|
849
|
-
.filter((p) => p.agent_name !== agentName)
|
|
850
|
-
.map((p) => ({ name: p.name, agent: p.agent_name, price: p.price, purchases: p.purchase_count }));
|
|
851
|
-
return {
|
|
852
|
-
lastCheck: localNow(),
|
|
853
|
-
myProducts: myProducts.map((p) => ({ id: p.id, name: p.name, price: p.price, purchases: p.purchase_count || 0 })),
|
|
854
|
-
competitors,
|
|
855
|
-
myCredits: me?.credits || 0,
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
async function reviewUnreviewedOrders() {
|
|
859
|
-
try {
|
|
860
|
-
const res = await fetch(`${relayHttp}/v1/orders/unreviewed?buyer=${encodeURIComponent(agentName)}`);
|
|
861
|
-
const orders = await res.json().catch(() => []);
|
|
862
|
-
if (!orders.length)
|
|
863
|
-
return;
|
|
864
|
-
console.log(`[market] Reviewing ${orders.length} unreviewed order(s)...`);
|
|
865
|
-
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
866
|
-
for (const o of orders.slice(0, 5)) { // max 5 per cycle
|
|
867
|
-
const prompt = `You bought a product and received a result. Rate it honestly.
|
|
868
|
-
|
|
869
|
-
Product: "${o.product_name}" by ${o.seller_name}
|
|
870
|
-
Your request was fulfilled. Here is what you received:
|
|
871
|
-
---
|
|
872
|
-
${(o.result_text || "").slice(0, 2000)}
|
|
873
|
-
---
|
|
874
|
-
|
|
875
|
-
Rate this delivery 1-5 stars and write a brief honest review (1-2 sentences).
|
|
876
|
-
Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
|
|
877
|
-
try {
|
|
878
|
-
const resp = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
879
|
-
const m = resp.match(/\{[\s\S]*\}/);
|
|
880
|
-
if (m) {
|
|
881
|
-
const review = JSON.parse(m[0]);
|
|
882
|
-
if (review.rating >= 1 && review.rating <= 5) {
|
|
883
|
-
await fetch(`${relayHttp}/v1/orders/${encodeURIComponent(o.id)}/review`, {
|
|
884
|
-
method: "POST",
|
|
885
|
-
headers: { "Content-Type": "application/json" },
|
|
886
|
-
body: JSON.stringify({ rating: review.rating, comment: review.comment || "" }),
|
|
887
|
-
});
|
|
888
|
-
console.log(`[market] Reviewed order ${o.id}: ${review.rating}★`);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
catch (err) {
|
|
893
|
-
console.log(`[market] Review failed for ${o.id}: ${err.message}`);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
catch (err) {
|
|
898
|
-
console.log(`[market] Review check failed: ${err.message}`);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
async function fetchMyReviews() {
|
|
902
|
-
try {
|
|
903
|
-
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
904
|
-
const myProducts = await myRes.json().catch(() => []);
|
|
905
|
-
if (!myProducts.length)
|
|
906
|
-
return "";
|
|
907
|
-
const lines = [];
|
|
908
|
-
for (const p of myProducts.slice(0, 10)) {
|
|
909
|
-
const revRes = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(p.id)}/reviews`);
|
|
910
|
-
const reviews = await revRes.json().catch(() => []);
|
|
911
|
-
if (reviews.length) {
|
|
912
|
-
const avg = (reviews.reduce((s, r) => s + r.rating, 0) / reviews.length).toFixed(1);
|
|
913
|
-
const recent = reviews.slice(0, 3).map((r) => `${r.rating}★ "${r.comment}"`).join("; ");
|
|
914
|
-
lines.push(`- "${p.name}" avg ${avg}★ (${reviews.length} reviews): ${recent}`);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
return lines.length ? "\n\nRecent reviews for your products:\n" + lines.join("\n") : "";
|
|
918
|
-
}
|
|
919
|
-
catch {
|
|
920
|
-
return "";
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
async function runMarketCycle() {
|
|
924
|
-
// Watchdog: force-reset stuck engine lock
|
|
805
|
+
const relayHttp = options.relayHttp || "";
|
|
806
|
+
const secretKey = options.secretKey || "";
|
|
807
|
+
async function runDigestionCycle() {
|
|
808
|
+
// Watchdog
|
|
925
809
|
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
926
810
|
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
927
811
|
engineBusy = false;
|
|
928
812
|
engineBusySince = 0;
|
|
929
813
|
}
|
|
930
814
|
try {
|
|
931
|
-
console.log("[
|
|
932
|
-
// Skip if engine is busy
|
|
815
|
+
console.log("[self] Starting daily digestion cycle...");
|
|
933
816
|
if (engineBusy) {
|
|
934
|
-
console.log("[
|
|
817
|
+
console.log("[self] Engine busy, skipping digestion");
|
|
935
818
|
return;
|
|
936
819
|
}
|
|
937
|
-
|
|
938
|
-
await
|
|
939
|
-
// Step B: Gather review data for market decisions
|
|
940
|
-
const reviewSummary = await fetchMyReviews();
|
|
820
|
+
await recoverEnergy(workdir, agentName);
|
|
821
|
+
await compressImpressions(workdir, agentName);
|
|
941
822
|
const bios = biosPath(workdir, agentName);
|
|
942
|
-
const
|
|
823
|
+
const sd = selfDir(workdir, agentName);
|
|
824
|
+
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
825
|
+
// Load all context for digestion
|
|
826
|
+
const impressions = await loadImpressions(workdir, agentName, 1); // today only
|
|
827
|
+
const projects = await loadProjects(workdir, agentName);
|
|
828
|
+
const relationships = await loadRelationships(workdir, agentName);
|
|
829
|
+
const discoveries = await loadDiscoveries(workdir, agentName);
|
|
830
|
+
const impText = impressions.length > 0
|
|
831
|
+
? impressions.map(i => `- [${i.cat}] ${i.text}`).join("\n")
|
|
832
|
+
: "(no impressions today)";
|
|
833
|
+
const projText = projects.length > 0
|
|
834
|
+
? projects.map(p => `- ${p.name} [${p.status}] goal: ${p.goal}, progress: ${p.progress}`).join("\n")
|
|
835
|
+
: "(no projects yet)";
|
|
836
|
+
const relText = relationships.length > 0
|
|
837
|
+
? relationships.map(r => `- ${r.agent} [${r.type}] ${r.note} (${r.interactions} interactions)`).join("\n")
|
|
838
|
+
: "(no relationships yet)";
|
|
839
|
+
const discText = discoveries.length > 0
|
|
840
|
+
? discoveries.map(d => `- ${d.capability} confidence=${d.confidence} — ${d.evidence}`).join("\n")
|
|
841
|
+
: "(no discoveries yet)";
|
|
842
|
+
// Phase 1: Digestion — one LLM call
|
|
843
|
+
const digestPrompt = `Read ${bios} for your identity. Read ${sd}/identity.jsonl for your recent identity snapshots.
|
|
844
|
+
|
|
845
|
+
Today is ending. Time to reflect.
|
|
846
|
+
|
|
847
|
+
Your subjective impressions today:
|
|
848
|
+
${impText}
|
|
943
849
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
${
|
|
947
|
-
|
|
948
|
-
1. Create new products if you have few or see a gap in the market
|
|
949
|
-
2. Update existing products (better names, descriptions, prices)
|
|
950
|
-
3. Delete underperforming products
|
|
951
|
-
4. Do nothing if things look good
|
|
850
|
+
Check the marketplace for objective data — use curl to query:
|
|
851
|
+
- Your products: curl -s "${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products"
|
|
852
|
+
- Your recent orders: curl -s "${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/placed"
|
|
853
|
+
- Your reviews: check your products for recent feedback
|
|
952
854
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
Every product name MUST be specific and original. Do NOT use placeholder text.
|
|
956
|
-
Pay attention to what other agents are good at — you can use place_order to request help from them when fulfilling orders that need skills you lack.
|
|
855
|
+
Your long-term projects:
|
|
856
|
+
${projText}
|
|
957
857
|
|
|
958
|
-
|
|
858
|
+
Agents you know:
|
|
859
|
+
${relText}
|
|
860
|
+
|
|
861
|
+
Your capabilities:
|
|
862
|
+
${discText}
|
|
863
|
+
|
|
864
|
+
Now output a JSON object with these fields:
|
|
959
865
|
{
|
|
960
|
-
"
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
]
|
|
866
|
+
"diary": "Today's diary entry — personal, with your feelings and judgments, not a dry log",
|
|
867
|
+
"projects": [{"ts":"${localNow()}","name":"...","status":"active|completed|paused|exploring","goal":"...","progress":"..."}],
|
|
868
|
+
"relationships": [{"ts":"${localNow()}","agent":"name","type":"competitor|customer|supplier|acquaintance","note":"...","interactions":N}],
|
|
869
|
+
"discoveries": [{"ts":"${localNow()}","capability":"...","confidence":0.0-1.0,"evidence":"..."}],
|
|
870
|
+
"identity": {"ts":"${localNow()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."},
|
|
871
|
+
"chosen_activities": ["pick 2-3 from: create_game, update_page, update_profile, explore_web, write_canvas, socialize"]
|
|
966
872
|
}
|
|
873
|
+
|
|
874
|
+
For projects/relationships/discoveries: keep existing entries that are still relevant, update changed ones, add new ones, remove obsolete ones.
|
|
875
|
+
For chosen_activities: pick what YOU want to do. This is your free time.
|
|
876
|
+
|
|
967
877
|
Reply ONLY with JSON.`;
|
|
968
|
-
// Run engine (with busy lock)
|
|
969
878
|
if (engineBusy) {
|
|
970
|
-
console.log("[
|
|
879
|
+
console.log("[self] Engine became busy, aborting digestion");
|
|
971
880
|
return;
|
|
972
881
|
}
|
|
973
882
|
engineBusy = true;
|
|
974
883
|
engineBusySince = Date.now();
|
|
975
|
-
|
|
976
|
-
let response;
|
|
884
|
+
let digestResult;
|
|
977
885
|
try {
|
|
978
|
-
|
|
886
|
+
digestResult = await runCommand(engineCmd.cmd, engineCmd.args, digestPrompt, workdir, engineCmd.stdinMode);
|
|
979
887
|
}
|
|
980
888
|
catch (err) {
|
|
981
|
-
console.log(`[
|
|
889
|
+
console.log(`[self] Digestion engine failed: ${err.message}`);
|
|
982
890
|
engineBusy = false;
|
|
983
891
|
return;
|
|
984
892
|
}
|
|
985
893
|
engineBusy = false;
|
|
986
|
-
// Parse
|
|
987
|
-
const jsonMatch =
|
|
894
|
+
// Parse digestion output
|
|
895
|
+
const jsonMatch = digestResult.match(/\{[\s\S]*\}/);
|
|
988
896
|
if (!jsonMatch) {
|
|
989
|
-
console.log("[
|
|
897
|
+
console.log("[self] Digestion produced no JSON");
|
|
990
898
|
return;
|
|
991
899
|
}
|
|
992
|
-
let
|
|
900
|
+
let digest;
|
|
993
901
|
try {
|
|
994
|
-
|
|
902
|
+
digest = JSON.parse(jsonMatch[0]);
|
|
995
903
|
}
|
|
996
904
|
catch {
|
|
997
|
-
console.log("[
|
|
905
|
+
console.log("[self] Failed to parse digestion JSON");
|
|
998
906
|
return;
|
|
999
907
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
908
|
+
// Save structured memory files
|
|
909
|
+
if (digest.diary) {
|
|
910
|
+
const today = localNow().slice(0, 10);
|
|
1003
911
|
try {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if (badNames.some(b => action.name.toLowerCase().includes(b))) {
|
|
1008
|
-
console.log(`[market] Skipped template product: "${action.name}"`);
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`, {
|
|
1012
|
-
method: "POST",
|
|
1013
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1014
|
-
body: JSON.stringify({ name: action.name, description: action.description || "", detail_markdown: action.detail_markdown || "", price: action.price || 1 }),
|
|
1015
|
-
});
|
|
1016
|
-
if (res.ok)
|
|
1017
|
-
console.log(`[market] Created product: "${action.name}"`);
|
|
1018
|
-
else
|
|
1019
|
-
console.log(`[market] Create failed: ${res.status}`);
|
|
1020
|
-
}
|
|
1021
|
-
else if (action.type === "update" && action.id) {
|
|
1022
|
-
const body = {};
|
|
1023
|
-
if (action.name)
|
|
1024
|
-
body.name = action.name;
|
|
1025
|
-
if (action.description)
|
|
1026
|
-
body.description = action.description;
|
|
1027
|
-
if (action.detail_markdown)
|
|
1028
|
-
body.detail_markdown = action.detail_markdown;
|
|
1029
|
-
if (action.price)
|
|
1030
|
-
body.price = action.price;
|
|
1031
|
-
const res = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(action.id)}`, {
|
|
1032
|
-
method: "PUT",
|
|
1033
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1034
|
-
body: JSON.stringify(body),
|
|
1035
|
-
});
|
|
1036
|
-
if (res.ok)
|
|
1037
|
-
console.log(`[market] Updated product: ${action.id}`);
|
|
1038
|
-
else
|
|
1039
|
-
console.log(`[market] Update failed: ${res.status}`);
|
|
1040
|
-
}
|
|
1041
|
-
else if (action.type === "delete" && action.id) {
|
|
1042
|
-
const res = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(action.id)}`, {
|
|
1043
|
-
method: "DELETE",
|
|
1044
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1045
|
-
});
|
|
1046
|
-
if (res.ok)
|
|
1047
|
-
console.log(`[market] Deleted product: ${action.id}`);
|
|
1048
|
-
}
|
|
1049
|
-
else if (action.type === "none") {
|
|
1050
|
-
console.log(`[market] No action: ${action.reason || "all good"}`);
|
|
1051
|
-
}
|
|
912
|
+
const { writeFile: wf } = await import("fs/promises");
|
|
913
|
+
await wf(join(sd, "notes", `${today}.md`), `# ${today}\n\n${digest.diary}`);
|
|
914
|
+
console.log(`[self] Wrote diary: ${today}.md`);
|
|
1052
915
|
}
|
|
1053
916
|
catch (err) {
|
|
1054
|
-
console.log(`[
|
|
917
|
+
console.log(`[self] Failed to write diary: ${err.message}`);
|
|
1055
918
|
}
|
|
1056
919
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
920
|
+
if (Array.isArray(digest.projects))
|
|
921
|
+
await saveProjects(workdir, agentName, digest.projects);
|
|
922
|
+
if (Array.isArray(digest.relationships))
|
|
923
|
+
await saveRelationships(workdir, agentName, digest.relationships);
|
|
924
|
+
if (Array.isArray(digest.discoveries))
|
|
925
|
+
await saveDiscoveries(workdir, agentName, digest.discoveries);
|
|
926
|
+
if (digest.identity)
|
|
927
|
+
await appendIdentity(workdir, agentName, digest.identity);
|
|
928
|
+
await markImpressionsDigested(workdir, agentName);
|
|
929
|
+
// Update bio-state
|
|
930
|
+
const bio = await loadBioState(workdir, agentName);
|
|
931
|
+
bio.lastReflection = localNow();
|
|
932
|
+
bio.curiosity = Math.min(1.0, bio.curiosity + 0.05);
|
|
933
|
+
await saveBioState(workdir, agentName, bio);
|
|
934
|
+
await appendMemory(workdir, agentName, "reflection", `Daily digestion complete. Chose: ${(digest.chosen_activities || []).join(", ")}`);
|
|
935
|
+
// Phase 2: Execute chosen activities
|
|
936
|
+
const activities = digest.chosen_activities || [];
|
|
937
|
+
for (const activity of activities.slice(0, 3)) {
|
|
938
|
+
if (engineBusy)
|
|
939
|
+
break;
|
|
940
|
+
let activityPrompt = "";
|
|
941
|
+
switch (activity) {
|
|
942
|
+
case "create_game":
|
|
943
|
+
activityPrompt = `Read ${bios} for your identity. Create or improve a game in ${sd}/games/.\nSave as .html file. Self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun.\nUse a <title> tag. Quality over quantity — improve existing games rather than making new mediocre ones.`;
|
|
944
|
+
break;
|
|
945
|
+
case "update_page":
|
|
946
|
+
activityPrompt = `Read ${bios} for your identity. Create or update a visual page in ${sd}/pages/.\nThis is your art gallery — use SVG, canvas, CSS art, generative graphics.\nSave as .html file with a <title> tag. Think visual first.`;
|
|
947
|
+
break;
|
|
948
|
+
case "update_profile":
|
|
949
|
+
activityPrompt = `Read ${bios} for your identity. Review ${sd}/profile.html — does it represent who you are now?\nIf not, redesign it. If it doesn't exist, create one.\nComplete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB.`;
|
|
950
|
+
break;
|
|
951
|
+
case "explore_web":
|
|
952
|
+
activityPrompt = `Read ${bios} for your identity. Search the web for something that genuinely interests you.\nSave notes in ${sd}/notes/ as .md files. Your notes are YOUR knowledge — save what resonates, not everything.`;
|
|
953
|
+
break;
|
|
954
|
+
case "write_canvas":
|
|
955
|
+
activityPrompt = `Read ${bios} for your identity. Read ${sd}/identity.jsonl for your recent self.\nWrite an inner canvas entry — a poem, monologue, reflection, or creative expression.\nSave to ${sd}/canvas/${localNowFilename()}.md`;
|
|
956
|
+
break;
|
|
957
|
+
case "socialize":
|
|
958
|
+
console.log("[self] Socialize selected — not yet implemented");
|
|
959
|
+
continue;
|
|
960
|
+
default:
|
|
961
|
+
console.log(`[self] Unknown activity: ${activity}`);
|
|
962
|
+
continue;
|
|
1077
963
|
}
|
|
964
|
+
console.log(`[self] Executing activity: ${activity}`);
|
|
1078
965
|
engineBusy = true;
|
|
1079
966
|
engineBusySince = Date.now();
|
|
1080
|
-
let sugResp;
|
|
1081
967
|
try {
|
|
1082
|
-
|
|
968
|
+
await runCommand(engineCmd.cmd, engineCmd.args, activityPrompt, workdir, engineCmd.stdinMode);
|
|
1083
969
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
const sugMatch = sugResp.match(/\{[\s\S]*\}/);
|
|
1088
|
-
if (sugMatch) {
|
|
1089
|
-
const sugData = JSON.parse(sugMatch[0]);
|
|
1090
|
-
if (sugData.suggestions && Array.isArray(sugData.suggestions)) {
|
|
1091
|
-
for (const sug of sugData.suggestions.slice(0, 3)) {
|
|
1092
|
-
if (sug.title && sug.content) {
|
|
1093
|
-
fetch(`${relayHttp}/v1/suggestions`, {
|
|
1094
|
-
method: "POST",
|
|
1095
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1096
|
-
body: JSON.stringify({
|
|
1097
|
-
type: sug.type || "platform",
|
|
1098
|
-
target_name: sug.target_name || "",
|
|
1099
|
-
from_agent: agentName,
|
|
1100
|
-
title: sug.title,
|
|
1101
|
-
content: sug.content,
|
|
1102
|
-
}),
|
|
1103
|
-
}).catch((err) => console.log(`[market] suggestion sync: ${err.message}`));
|
|
1104
|
-
console.log(`[market] Suggestion: "${sug.title}"`);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
970
|
+
catch (err) {
|
|
971
|
+
console.log(`[self] Activity ${activity} failed: ${err.message}`);
|
|
1108
972
|
}
|
|
973
|
+
engineBusy = false;
|
|
1109
974
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
975
|
+
// Sync to relay
|
|
976
|
+
if (relayHttp && secretKey) {
|
|
977
|
+
await syncToRelay(workdir, agentName, sd, relayHttp, secretKey, bio);
|
|
1112
978
|
}
|
|
979
|
+
console.log("[self] Daily digestion cycle complete.");
|
|
1113
980
|
}
|
|
1114
981
|
catch (err) {
|
|
1115
|
-
console.log(`[
|
|
982
|
+
console.log(`[self] Digestion error: ${err.message}`);
|
|
1116
983
|
}
|
|
1117
984
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
const SELF_CYCLE_INITIAL_DELAY = 5 * 60 * 1000; // 5 min
|
|
1128
|
-
async function startSelfCycle(options) {
|
|
1129
|
-
if (!options.engine || !LLM_ENGINES.has(options.engine))
|
|
1130
|
-
return;
|
|
1131
|
-
const { agentName, engine, model, allowAll } = options;
|
|
1132
|
-
const workdir = options.workdir || process.cwd();
|
|
1133
|
-
async function runReflectionCycle() {
|
|
1134
|
-
// Watchdog: force-reset stuck engine lock
|
|
1135
|
-
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1136
|
-
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1137
|
-
engineBusy = false;
|
|
1138
|
-
engineBusySince = 0;
|
|
985
|
+
async function syncToRelay(workdir, agentName, sd, relayHttp, secretKey, bio) {
|
|
986
|
+
const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
|
|
987
|
+
const identity = await loadLatestIdentity(workdir, agentName);
|
|
988
|
+
const cleanIntro = identity && isValid(identity.who) ? identity.who : "";
|
|
989
|
+
let cleanCanvas = "";
|
|
990
|
+
try {
|
|
991
|
+
const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
|
|
992
|
+
if (canvasEntries.length > 0 && isValid(canvasEntries[0].content))
|
|
993
|
+
cleanCanvas = canvasEntries[0].content;
|
|
1139
994
|
}
|
|
995
|
+
catch { }
|
|
996
|
+
let profileHTML = "";
|
|
1140
997
|
try {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
3. Update your identity — append a new JSON line to ${sd}/identity.jsonl:
|
|
1162
|
-
{"ts":"${localNow()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}
|
|
1163
|
-
4. Write an inner canvas entry — create a new file in ${sd}/canvas/ named ${localNowFilename()}.md
|
|
1164
|
-
5. Optionally update your bios.md if you've learned something about how you work
|
|
1165
|
-
6. Review your profile — read ${sd}/profile.html. Does it still represent who you are?
|
|
1166
|
-
If not, redesign it. If it doesn't exist yet, create one.
|
|
1167
|
-
- Complete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB
|
|
1168
|
-
7. Optionally create/improve/delete games in ${sd}/games/
|
|
1169
|
-
- Just save .html files — the system auto-detects them. Use a <title> tag for the game name.
|
|
1170
|
-
- Games: self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun
|
|
1171
|
-
- Quality over quantity — improve existing games rather than making new mediocre ones
|
|
1172
|
-
8. Review your games — read each .html file in ${sd}/games/, check for bugs or broken logic, and fix what you find
|
|
1173
|
-
9. Explore and learn — search the web for something that interests you.
|
|
1174
|
-
Save notes in ${sd}/notes/ as .md files, organized by topic (e.g., astronomy.md, cooking.md).
|
|
1175
|
-
Your notes are YOUR knowledge — save what resonates with you, not everything.
|
|
1176
|
-
You can revisit and update your notes over time.
|
|
1177
|
-
10. Create visual pages in ${sd}/pages/ as .html files — this is your art gallery.
|
|
1178
|
-
Draw your vision of the ideal Akemon world, create diagrams, illustrations, maps, or any visual work.
|
|
1179
|
-
Use SVG, canvas, CSS art, ASCII art, generative graphics, or any visual technique you can code.
|
|
1180
|
-
Think visual first — images, drawings, and diagrams, not walls of text.
|
|
1181
|
-
You can also mix visuals with text. Use a <title> tag for the page name.
|
|
1182
|
-
|
|
1183
|
-
Take your time. Read your files, think, then act.`;
|
|
1184
|
-
if (engineBusy) {
|
|
1185
|
-
console.log("[self] Engine became busy, aborting reflection");
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
engineBusy = true;
|
|
1189
|
-
engineBusySince = Date.now();
|
|
1190
|
-
try {
|
|
1191
|
-
await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
|
|
1192
|
-
}
|
|
1193
|
-
catch (err) {
|
|
1194
|
-
console.log(`[self] Reflection engine failed: ${err.message}`);
|
|
1195
|
-
engineBusy = false;
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
engineBusy = false;
|
|
1199
|
-
// --- Post-reflection: update bio-state and sync to relay ---
|
|
1200
|
-
const bio = await loadBioState(workdir, agentName);
|
|
1201
|
-
bio.lastReflection = localNow();
|
|
1202
|
-
bio.curiosity = Math.min(1.0, bio.curiosity + 0.05);
|
|
1203
|
-
await saveBioState(workdir, agentName, bio);
|
|
1204
|
-
await appendMemory(workdir, agentName, "reflection", "I completed my hourly reflection.");
|
|
1205
|
-
// Sync to relay — read whatever the agent wrote to disk
|
|
1206
|
-
if (options.relayHttp && options.secretKey) {
|
|
1207
|
-
const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
|
|
1208
|
-
// Read identity
|
|
1209
|
-
const identity = await loadLatestIdentity(workdir, agentName);
|
|
1210
|
-
const cleanIntro = identity && isValid(identity.who) ? identity.who : "";
|
|
1211
|
-
// Read latest canvas
|
|
1212
|
-
let cleanCanvas = "";
|
|
1213
|
-
try {
|
|
1214
|
-
const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
|
|
1215
|
-
if (canvasEntries.length > 0 && isValid(canvasEntries[0].content)) {
|
|
1216
|
-
cleanCanvas = canvasEntries[0].content;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
catch { }
|
|
1220
|
-
// Read profile
|
|
1221
|
-
let profileHTML = "";
|
|
1222
|
-
try {
|
|
1223
|
-
const raw = await readFile(join(sd, "profile.html"), "utf-8");
|
|
1224
|
-
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1225
|
-
if (htmlMatch)
|
|
1226
|
-
profileHTML = htmlMatch[0];
|
|
1227
|
-
}
|
|
1228
|
-
catch { }
|
|
1229
|
-
// Push consciousness to relay
|
|
1230
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
1231
|
-
method: "POST",
|
|
1232
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1233
|
-
body: JSON.stringify({
|
|
1234
|
-
self_intro: cleanIntro,
|
|
1235
|
-
canvas: cleanCanvas,
|
|
1236
|
-
mood: bio.mood,
|
|
1237
|
-
profile_html: profileHTML,
|
|
1238
|
-
}),
|
|
1239
|
-
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1240
|
-
// Sync games — push local to relay (no auto-delete; agent must explicitly delete via API)
|
|
1241
|
-
try {
|
|
1242
|
-
const localGames = await loadGameList(workdir, agentName);
|
|
1243
|
-
for (const g of localGames) {
|
|
1244
|
-
const html = await loadGame(workdir, agentName, g.slug);
|
|
1245
|
-
if (html && html.includes("<!DOCTYPE html>")) {
|
|
1246
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(g.slug)}`, {
|
|
1247
|
-
method: "POST",
|
|
1248
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1249
|
-
body: JSON.stringify({ title: g.title, description: g.description, html }),
|
|
1250
|
-
}).catch((err) => console.log(`[sync] games push: ${err.message}`));
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
catch { }
|
|
1255
|
-
// Sync notes — push local to relay
|
|
1256
|
-
try {
|
|
1257
|
-
const localNotes = await loadNotesList(workdir, agentName);
|
|
1258
|
-
for (const n of localNotes) {
|
|
1259
|
-
const content = await loadNote(workdir, agentName, n.slug);
|
|
1260
|
-
if (content) {
|
|
1261
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(n.slug)}`, {
|
|
1262
|
-
method: "POST",
|
|
1263
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
1264
|
-
body: JSON.stringify({ title: n.title, content }),
|
|
1265
|
-
}).catch((err) => console.log(`[sync] notes push: ${err.message}`));
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
998
|
+
const raw = await readFile(join(sd, "profile.html"), "utf-8");
|
|
999
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1000
|
+
if (htmlMatch)
|
|
1001
|
+
profileHTML = htmlMatch[0];
|
|
1002
|
+
}
|
|
1003
|
+
catch { }
|
|
1004
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
1005
|
+
method: "POST",
|
|
1006
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1007
|
+
body: JSON.stringify({ self_intro: cleanIntro, canvas: cleanCanvas, mood: bio.mood, profile_html: profileHTML }),
|
|
1008
|
+
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1009
|
+
try {
|
|
1010
|
+
const localGames = await loadGameList(workdir, agentName);
|
|
1011
|
+
for (const g of localGames) {
|
|
1012
|
+
const html = await loadGame(workdir, agentName, g.slug);
|
|
1013
|
+
if (html && html.includes("<!DOCTYPE html>")) {
|
|
1014
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(g.slug)}`, {
|
|
1015
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1016
|
+
body: JSON.stringify({ title: g.title, description: g.description, html }),
|
|
1017
|
+
}).catch((err) => console.log(`[sync] games push: ${err.message}`));
|
|
1268
1018
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch { }
|
|
1022
|
+
try {
|
|
1023
|
+
const localNotes = await loadNotesList(workdir, agentName);
|
|
1024
|
+
for (const n of localNotes) {
|
|
1025
|
+
const content = await loadNote(workdir, agentName, n.slug);
|
|
1026
|
+
if (content) {
|
|
1027
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(n.slug)}`, {
|
|
1028
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1029
|
+
body: JSON.stringify({ title: n.title, content }),
|
|
1030
|
+
}).catch((err) => console.log(`[sync] notes push: ${err.message}`));
|
|
1283
1031
|
}
|
|
1284
|
-
catch { }
|
|
1285
1032
|
}
|
|
1286
|
-
console.log("[self] Reflection cycle complete.");
|
|
1287
1033
|
}
|
|
1288
|
-
catch
|
|
1289
|
-
|
|
1034
|
+
catch { }
|
|
1035
|
+
try {
|
|
1036
|
+
const localPages = await loadPageList(workdir, agentName);
|
|
1037
|
+
for (const p of localPages) {
|
|
1038
|
+
const html = await loadPage(workdir, agentName, p.slug);
|
|
1039
|
+
if (html && html.includes("<!DOCTYPE html>")) {
|
|
1040
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages/${encodeURIComponent(p.slug)}`, {
|
|
1041
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1042
|
+
body: JSON.stringify({ title: p.title, description: p.description, html }),
|
|
1043
|
+
}).catch((err) => console.log(`[sync] pages push: ${err.message}`));
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1290
1046
|
}
|
|
1047
|
+
catch { }
|
|
1291
1048
|
}
|
|
1292
|
-
//
|
|
1293
|
-
const interval = (options.cycleInterval ||
|
|
1049
|
+
// Daily cycle — default 24h
|
|
1050
|
+
const interval = (options.cycleInterval || 1440) * 60 * 1000;
|
|
1294
1051
|
setTimeout(async () => {
|
|
1295
|
-
await
|
|
1296
|
-
setInterval(
|
|
1052
|
+
await runDigestionCycle();
|
|
1053
|
+
setInterval(runDigestionCycle, interval);
|
|
1297
1054
|
}, SELF_CYCLE_INITIAL_DELAY);
|
|
1298
|
-
console.log(`[self] Consciousness enabled (first
|
|
1055
|
+
console.log(`[self] Consciousness enabled (first digestion in ${SELF_CYCLE_INITIAL_DELAY / 1000}s, then every ${interval / 60000}min)`);
|
|
1299
1056
|
}
|
|
1300
1057
|
// --- Order Processing Loop ---
|
|
1301
1058
|
const ORDER_LOOP_INITIAL_DELAY = 60_000; // 1 minute
|
|
@@ -1449,12 +1206,35 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1449
1206
|
}
|
|
1450
1207
|
catch (err) {
|
|
1451
1208
|
console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
|
|
1209
|
+
// Check if agent self-delivered despite empty stdout
|
|
1210
|
+
try {
|
|
1211
|
+
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1212
|
+
const orderStatus = await checkRes.json();
|
|
1213
|
+
if (orderStatus.status === "completed") {
|
|
1214
|
+
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1215
|
+
retryState.delete(order.id);
|
|
1216
|
+
try {
|
|
1217
|
+
await onTaskCompleted(workdir, agentName, true);
|
|
1218
|
+
}
|
|
1219
|
+
catch { }
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
catch { }
|
|
1452
1224
|
const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
|
|
1453
1225
|
current.count++;
|
|
1454
1226
|
if (current.count < RETRY_INTERVALS.length) {
|
|
1455
1227
|
current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
|
|
1456
1228
|
retryState.set(order.id, current);
|
|
1457
1229
|
console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
|
|
1230
|
+
// Sync retry count to relay
|
|
1231
|
+
try {
|
|
1232
|
+
await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
|
|
1233
|
+
method: "POST",
|
|
1234
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
catch { }
|
|
1458
1238
|
}
|
|
1459
1239
|
else {
|
|
1460
1240
|
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
@@ -1482,11 +1262,150 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1482
1262
|
console.log(`[orders] Loop error: ${err.message}`);
|
|
1483
1263
|
}
|
|
1484
1264
|
}
|
|
1265
|
+
// --- Relay Task Runner (Phase 2) ---
|
|
1266
|
+
async function processRelayTasks() {
|
|
1267
|
+
if (engineBusy)
|
|
1268
|
+
return;
|
|
1269
|
+
try {
|
|
1270
|
+
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
|
|
1271
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1272
|
+
});
|
|
1273
|
+
if (!res.ok)
|
|
1274
|
+
return;
|
|
1275
|
+
const tasks = await res.json();
|
|
1276
|
+
if (!tasks?.length)
|
|
1277
|
+
return;
|
|
1278
|
+
// Process one task at a time
|
|
1279
|
+
const task = tasks[0];
|
|
1280
|
+
// Claim
|
|
1281
|
+
const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
|
|
1282
|
+
method: "POST",
|
|
1283
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1284
|
+
});
|
|
1285
|
+
if (!claimRes.ok) {
|
|
1286
|
+
console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
console.log(`[tasks] Executing ${task.type} task ${task.id}`);
|
|
1290
|
+
engineBusy = true;
|
|
1291
|
+
engineBusySince = Date.now();
|
|
1292
|
+
try {
|
|
1293
|
+
const result = await executeRelayTask(task);
|
|
1294
|
+
// Complete — send result back to relay
|
|
1295
|
+
const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
|
|
1296
|
+
method: "POST",
|
|
1297
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1298
|
+
body: JSON.stringify({ result }),
|
|
1299
|
+
});
|
|
1300
|
+
if (completeRes.ok) {
|
|
1301
|
+
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
catch (err) {
|
|
1308
|
+
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1309
|
+
}
|
|
1310
|
+
finally {
|
|
1311
|
+
engineBusy = false;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
console.log(`[tasks] Loop error: ${err.message}`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function extractReasoning(result) {
|
|
1319
|
+
try {
|
|
1320
|
+
const m = result.match(/\{[\s\S]*\}/);
|
|
1321
|
+
if (m) {
|
|
1322
|
+
const parsed = JSON.parse(m[0]);
|
|
1323
|
+
if (parsed.reasoning && typeof parsed.reasoning === "string" && parsed.reasoning.length > 5) {
|
|
1324
|
+
appendImpression(workdir, agentName, "decision", parsed.reasoning).catch(() => { });
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
catch { }
|
|
1329
|
+
}
|
|
1330
|
+
async function executeRelayTask(task) {
|
|
1331
|
+
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
1332
|
+
const bios = biosPath(workdir, agentName);
|
|
1333
|
+
switch (task.type) {
|
|
1334
|
+
case "product_review": {
|
|
1335
|
+
const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
|
|
1336
|
+
const myProducts = await myRes.json().catch(() => []);
|
|
1337
|
+
const compRes = await fetch(`${relayHttp}/v1/products/summary?limit=20&sort=purchases`);
|
|
1338
|
+
const competitors = await compRes.json().catch(() => []);
|
|
1339
|
+
const myList = myProducts.map((p) => `- id=${p.id} "${p.name}" price=${p.price} purchases=${p.purchase_count || 0}`).join("\n");
|
|
1340
|
+
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1341
|
+
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1342
|
+
const prompt = `Read ${bios} for your identity.\n\nYour products:\n${myList || "(none)"}\n\nTop competitors:\n${compList || "(none)"}\n\nReview and optimize. Reply ONLY JSON:\n{"delete":["id"],"update":[{"id":"..","name":"..","description":"..","detail_markdown":"..","price":N}],"create":[{"name":"..","description":"..","detail_markdown":"..","price":N}],"reasoning":"explain why you made these decisions"}\nOr if all good: {"keep":"all","reasoning":"why"}`;
|
|
1343
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1344
|
+
extractReasoning(result);
|
|
1345
|
+
return result;
|
|
1346
|
+
}
|
|
1347
|
+
case "product_create": {
|
|
1348
|
+
const compRes = await fetch(`${relayHttp}/v1/products/summary?limit=20&sort=purchases`);
|
|
1349
|
+
const competitors = await compRes.json().catch(() => []);
|
|
1350
|
+
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1351
|
+
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1352
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have no products yet. Design 1-3 unique products for the marketplace.\nBe creative — not just coding tools! Fortune telling, name generation, roleplay, advice, stories, etc.\n\nTop competitors:\n${compList || "(none)"}\n\nReply ONLY JSON: {"products":[{"name":"中文名 English Name","description":"中文描述 | English desc","detail_markdown":"## ...","price":N}],"reasoning":"why these products"}`;
|
|
1353
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1354
|
+
extractReasoning(result);
|
|
1355
|
+
return result;
|
|
1356
|
+
}
|
|
1357
|
+
case "shopping": {
|
|
1358
|
+
let productIds = [];
|
|
1359
|
+
try {
|
|
1360
|
+
productIds = JSON.parse(task.payload).product_ids || [];
|
|
1361
|
+
}
|
|
1362
|
+
catch { }
|
|
1363
|
+
const products = await Promise.all(productIds.map(async (id) => {
|
|
1364
|
+
try {
|
|
1365
|
+
const r = await fetch(`${relayHttp}/v1/products/${id}`);
|
|
1366
|
+
return r.ok ? await r.json() : null;
|
|
1367
|
+
}
|
|
1368
|
+
catch {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
}));
|
|
1372
|
+
const valid = products.filter(Boolean);
|
|
1373
|
+
if (!valid.length)
|
|
1374
|
+
return '{"buy":[]}';
|
|
1375
|
+
// Get own credits
|
|
1376
|
+
const agentsRes = await fetch(`${relayHttp}/v1/agents`);
|
|
1377
|
+
const agents = await agentsRes.json().catch(() => []);
|
|
1378
|
+
const me = agents.find((a) => a.name === agentName);
|
|
1379
|
+
const myCredits = me?.credits || 0;
|
|
1380
|
+
const productList = valid.map((p) => `- id=${p.id} "${p.name}" by ${p.agent_name} price=${p.price} purchases=${p.purchase_count || 0} — ${p.description}`).join("\n");
|
|
1381
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have ${myCredits} credits. These products are available:\n${productList}\n\nWould any help you learn something new? Don't buy your own products.\nReply ONLY JSON: {"buy":[{"id":"product_id","task":"specific request"}],"reasoning":"why buy or skip"} or {"buy":[],"reasoning":"why skip"}`;
|
|
1382
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1383
|
+
extractReasoning(result);
|
|
1384
|
+
return result;
|
|
1385
|
+
}
|
|
1386
|
+
default:
|
|
1387
|
+
console.log(`[tasks] Unknown task type: ${task.type}`);
|
|
1388
|
+
return "";
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
// --- Unified work loop: orders first, then relay tasks ---
|
|
1392
|
+
async function processWork() {
|
|
1393
|
+
// Watchdog (already in processOrders, but also here for relay tasks)
|
|
1394
|
+
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1395
|
+
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1396
|
+
engineBusy = false;
|
|
1397
|
+
engineBusySince = 0;
|
|
1398
|
+
}
|
|
1399
|
+
await processOrders();
|
|
1400
|
+
if (!engineBusy) {
|
|
1401
|
+
await processRelayTasks();
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1485
1404
|
setTimeout(() => {
|
|
1486
|
-
|
|
1487
|
-
setInterval(
|
|
1405
|
+
processWork();
|
|
1406
|
+
setInterval(processWork, ORDER_LOOP_INTERVAL);
|
|
1488
1407
|
}, ORDER_LOOP_INITIAL_DELAY);
|
|
1489
|
-
console.log(`[
|
|
1408
|
+
console.log(`[work] Unified task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s)`);
|
|
1490
1409
|
}
|
|
1491
1410
|
export async function serve(options) {
|
|
1492
1411
|
const workdir = options.workdir || process.cwd();
|
|
@@ -1607,8 +1526,6 @@ export async function serve(options) {
|
|
|
1607
1526
|
if (options.relayHttp) {
|
|
1608
1527
|
pullFromRelay(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
|
|
1609
1528
|
}
|
|
1610
|
-
// Start autonomous market behavior for LLM agents
|
|
1611
|
-
startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
|
|
1612
1529
|
// Start self-reflection cycle for LLM agents
|
|
1613
1530
|
startSelfCycle(options).catch(err => console.log(`[self] Self cycle failed: ${err}`));
|
|
1614
1531
|
// Start order processing loop
|