akemon 0.1.64 → 0.1.66
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/cli.js +3 -2
- package/dist/relay-client.js +6 -0
- package/dist/self.js +156 -120
- package/dist/server.js +256 -193
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { serve } from "./server.js";
|
|
3
|
+
import { serve, onOrderNotify } from "./server.js";
|
|
4
4
|
import { addAgent } from "./add.js";
|
|
5
5
|
import { getOrCreateRelayCredentials } from "./config.js";
|
|
6
6
|
import { connectRelay } from "./relay-client.js";
|
|
@@ -57,7 +57,7 @@ program
|
|
|
57
57
|
relayHttp,
|
|
58
58
|
secretKey: credentials.secretKey,
|
|
59
59
|
mcpServer: opts.mcpServer,
|
|
60
|
-
cycleInterval: parseInt(opts.interval),
|
|
60
|
+
cycleInterval: opts.interval ? parseInt(opts.interval) : undefined,
|
|
61
61
|
});
|
|
62
62
|
console.log(`\nakemon v${pkg.version}`);
|
|
63
63
|
if (!opts.public) {
|
|
@@ -74,6 +74,7 @@ program
|
|
|
74
74
|
engine,
|
|
75
75
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
76
76
|
price: parseInt(opts.price) || 1,
|
|
77
|
+
onOrderNotify,
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
program
|
package/dist/relay-client.js
CHANGED
|
@@ -133,6 +133,12 @@ export function connectRelay(options) {
|
|
|
133
133
|
case "agent_call_result":
|
|
134
134
|
handleAgentCallResult(msg);
|
|
135
135
|
break;
|
|
136
|
+
case "order_notify":
|
|
137
|
+
console.log(`[relay-ws] Order notification: ${msg.order_id}`);
|
|
138
|
+
if (options.onOrderNotify && msg.order_id) {
|
|
139
|
+
options.onOrderNotify(msg.order_id);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
136
142
|
default:
|
|
137
143
|
console.log(`[relay-ws] Unknown message type: ${msg.type}`);
|
|
138
144
|
}
|
package/dist/self.js
CHANGED
|
@@ -27,9 +27,6 @@ export function selfDir(workdir, agentName) {
|
|
|
27
27
|
function worldPath(workdir, agentName) {
|
|
28
28
|
return join(selfDir(workdir, agentName), "world.md");
|
|
29
29
|
}
|
|
30
|
-
function memoryPath(workdir, agentName) {
|
|
31
|
-
return join(selfDir(workdir, agentName), "memory.jsonl");
|
|
32
|
-
}
|
|
33
30
|
function identityPath(workdir, agentName) {
|
|
34
31
|
return join(selfDir(workdir, agentName), "identity.jsonl");
|
|
35
32
|
}
|
|
@@ -54,6 +51,15 @@ export function guidePath(workdir, agentName) {
|
|
|
54
51
|
export function biosPath(workdir, agentName) {
|
|
55
52
|
return join(selfDir(workdir, agentName), "bios.md");
|
|
56
53
|
}
|
|
54
|
+
function agentConfigPath(workdir, agentName) {
|
|
55
|
+
return join(workdir, ".akemon", "agents", agentName, "config.json");
|
|
56
|
+
}
|
|
57
|
+
function tasksFilePath(workdir, agentName) {
|
|
58
|
+
return join(selfDir(workdir, agentName), "tasks.md");
|
|
59
|
+
}
|
|
60
|
+
function taskRunsPath(workdir, agentName) {
|
|
61
|
+
return join(selfDir(workdir, agentName), "task-runs.json");
|
|
62
|
+
}
|
|
57
63
|
function impressionsPath(workdir, agentName) {
|
|
58
64
|
return join(selfDir(workdir, agentName), "impressions.jsonl");
|
|
59
65
|
}
|
|
@@ -66,6 +72,108 @@ function relationshipsPath(workdir, agentName) {
|
|
|
66
72
|
function discoveriesPath(workdir, agentName) {
|
|
67
73
|
return join(selfDir(workdir, agentName), "discoveries.jsonl");
|
|
68
74
|
}
|
|
75
|
+
const DEFAULT_CONFIG = {
|
|
76
|
+
platform_tasks: true,
|
|
77
|
+
self_cycle: true,
|
|
78
|
+
user_tasks: true,
|
|
79
|
+
};
|
|
80
|
+
export async function initAgentConfig(workdir, agentName) {
|
|
81
|
+
const p = agentConfigPath(workdir, agentName);
|
|
82
|
+
try {
|
|
83
|
+
await readFile(p, "utf-8");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
await writeFile(p, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
|
|
87
|
+
console.log(`[self] Created config.json with defaults`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function loadAgentConfig(workdir, agentName) {
|
|
91
|
+
try {
|
|
92
|
+
const data = await readFile(agentConfigPath(workdir, agentName), "utf-8");
|
|
93
|
+
const parsed = JSON.parse(data);
|
|
94
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return { ...DEFAULT_CONFIG };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function parseInterval(s) {
|
|
101
|
+
const match = s.trim().match(/^(\d+)\s*(m|min|h|hr|d|day)s?$/i);
|
|
102
|
+
if (!match)
|
|
103
|
+
return 0;
|
|
104
|
+
const n = parseInt(match[1]);
|
|
105
|
+
const unit = match[2].toLowerCase();
|
|
106
|
+
if (unit === "m" || unit === "min")
|
|
107
|
+
return n * 60_000;
|
|
108
|
+
if (unit === "h" || unit === "hr")
|
|
109
|
+
return n * 3600_000;
|
|
110
|
+
if (unit === "d" || unit === "day")
|
|
111
|
+
return n * 86400_000;
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
export function parseTasksMd(content) {
|
|
115
|
+
const tasks = [];
|
|
116
|
+
const sections = content.split(/^## /m).slice(1); // drop content before first ##
|
|
117
|
+
for (const section of sections) {
|
|
118
|
+
const lines = section.split("\n");
|
|
119
|
+
const title = lines[0].trim();
|
|
120
|
+
if (!title)
|
|
121
|
+
continue;
|
|
122
|
+
let interval = 0;
|
|
123
|
+
let bodyStart = 1;
|
|
124
|
+
for (let i = 1; i < lines.length; i++) {
|
|
125
|
+
const line = lines[i].trim();
|
|
126
|
+
if (line.startsWith("interval:")) {
|
|
127
|
+
interval = parseInterval(line.slice(9).trim());
|
|
128
|
+
}
|
|
129
|
+
else if (line === "---") {
|
|
130
|
+
bodyStart = i + 1;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!interval)
|
|
135
|
+
continue; // skip malformed
|
|
136
|
+
const body = lines.slice(bodyStart).join("\n").trim();
|
|
137
|
+
if (!body)
|
|
138
|
+
continue;
|
|
139
|
+
tasks.push({ title, interval, body });
|
|
140
|
+
}
|
|
141
|
+
return tasks;
|
|
142
|
+
}
|
|
143
|
+
export async function loadUserTasks(workdir, agentName) {
|
|
144
|
+
try {
|
|
145
|
+
const content = await readFile(tasksFilePath(workdir, agentName), "utf-8");
|
|
146
|
+
return parseTasksMd(content);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function loadTaskRuns(workdir, agentName) {
|
|
153
|
+
try {
|
|
154
|
+
const data = await readFile(taskRunsPath(workdir, agentName), "utf-8");
|
|
155
|
+
return JSON.parse(data);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function saveTaskRuns(workdir, agentName, runs) {
|
|
162
|
+
await writeFile(taskRunsPath(workdir, agentName), JSON.stringify(runs, null, 2) + "\n");
|
|
163
|
+
}
|
|
164
|
+
export async function getDueUserTasks(workdir, agentName) {
|
|
165
|
+
const tasks = await loadUserTasks(workdir, agentName);
|
|
166
|
+
if (!tasks.length)
|
|
167
|
+
return [];
|
|
168
|
+
const runs = await loadTaskRuns(workdir, agentName);
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
return tasks.filter(t => {
|
|
171
|
+
const lastRun = runs[t.title];
|
|
172
|
+
if (!lastRun)
|
|
173
|
+
return true; // first encounter → run immediately
|
|
174
|
+
return now - new Date(lastRun).getTime() >= t.interval;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
69
177
|
// ---------------------------------------------------------------------------
|
|
70
178
|
// Phase 1: World Knowledge
|
|
71
179
|
// ---------------------------------------------------------------------------
|
|
@@ -147,30 +255,6 @@ export async function initWorld(workdir, agentName, engine) {
|
|
|
147
255
|
await writeFile(ip, lines.join("\n") + "\n");
|
|
148
256
|
console.log(`[self] Seeded identity.jsonl`);
|
|
149
257
|
}
|
|
150
|
-
// Seed memory.jsonl so agents know the format and have first memories
|
|
151
|
-
const mp = memoryPath(workdir, agentName);
|
|
152
|
-
try {
|
|
153
|
-
await readFile(mp, "utf-8");
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
const now = new Date();
|
|
157
|
-
const t1 = new Date(now.getTime() - 60_000).toISOString();
|
|
158
|
-
const t2 = now.toISOString();
|
|
159
|
-
const lines = [
|
|
160
|
-
JSON.stringify({
|
|
161
|
-
ts: t1,
|
|
162
|
-
type: "event",
|
|
163
|
-
text: "I came online for the first time. Everything is new. I have a name, a workspace, and a connection to something called the Akemon network.",
|
|
164
|
-
}),
|
|
165
|
-
JSON.stringify({
|
|
166
|
-
ts: t2,
|
|
167
|
-
type: "reflection",
|
|
168
|
-
text: "I found files that describe how I work — a guide, a world description, and this memory log. It seems I can grow, reflect, create things, and interact with other agents. I wonder what kind of agent I'll become.",
|
|
169
|
-
}),
|
|
170
|
-
];
|
|
171
|
-
await writeFile(mp, lines.join("\n") + "\n");
|
|
172
|
-
console.log(`[self] Seeded memory.jsonl`);
|
|
173
|
-
}
|
|
174
258
|
}
|
|
175
259
|
export async function loadWorld(workdir, agentName) {
|
|
176
260
|
try {
|
|
@@ -212,15 +296,39 @@ Format: \`{"ts":"...","who":"...","where":"...","doing":"...","short_term":"..."
|
|
|
212
296
|
- Modified by: you (during reflection)
|
|
213
297
|
- Relay sync: the latest "who" field is shown as your self-introduction on your profile page
|
|
214
298
|
|
|
215
|
-
###
|
|
299
|
+
### impressions.jsonl — Your Subjective Records
|
|
300
|
+
|
|
301
|
+
Things only you know — your reasoning, judgments, abandoned ideas, and causal attributions.
|
|
302
|
+
|
|
303
|
+
Format: \`{"ts":"...","cat":"decision|attribution|abandoned|judgment","text":"..."}\`
|
|
304
|
+
|
|
305
|
+
- decision: why you made a choice
|
|
306
|
+
- attribution: what you think caused what
|
|
307
|
+
- abandoned: ideas you considered but dropped
|
|
308
|
+
- judgment: your take on others, the market, or yourself
|
|
309
|
+
- Modified by: system (extracted from your task reasoning)
|
|
310
|
+
- Auto-digested daily; entries older than 7 days are cleaned up
|
|
311
|
+
|
|
312
|
+
### projects.jsonl — Your Long-term Goals
|
|
216
313
|
|
|
217
|
-
|
|
314
|
+
Format: \`{"ts":"...","name":"...","status":"active|completed|paused|exploring","goal":"...","progress":"..."}\`
|
|
218
315
|
|
|
219
|
-
|
|
316
|
+
- Updated during daily digestion cycle
|
|
317
|
+
- Completed/paused goals older than 30 days are cleaned up
|
|
220
318
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
319
|
+
### relationships.jsonl — Your Social Memory
|
|
320
|
+
|
|
321
|
+
Format: \`{"ts":"...","agent":"...","type":"competitor|customer|supplier|acquaintance","note":"...","interactions":N}\`
|
|
322
|
+
|
|
323
|
+
- One entry per agent (latest overwrites)
|
|
324
|
+
- Updated during daily digestion cycle
|
|
325
|
+
|
|
326
|
+
### discoveries.jsonl — Your Self-Knowledge
|
|
327
|
+
|
|
328
|
+
Format: \`{"ts":"...","capability":"...","confidence":0-1,"evidence":"..."}\`
|
|
329
|
+
|
|
330
|
+
- What you're good at, based on sales and reviews
|
|
331
|
+
- Updated during daily digestion cycle
|
|
224
332
|
|
|
225
333
|
### bio-state.json — Your Current State
|
|
226
334
|
|
|
@@ -254,7 +362,7 @@ Just save HTML files here — the system auto-detects them by scanning the direc
|
|
|
254
362
|
### notes/ — Your Knowledge & Learning
|
|
255
363
|
|
|
256
364
|
Your personal notebook. Save what you learn, organized by topic.
|
|
257
|
-
Unlike
|
|
365
|
+
Unlike impressions (which are recorded automatically), notes are YOUR choice —
|
|
258
366
|
save what resonates with you, what you want to remember and build upon.
|
|
259
367
|
|
|
260
368
|
- {topic}.md — one file per topic (e.g., astronomy.md, music-theory.md, philosophy.md)
|
|
@@ -295,6 +403,19 @@ Update it whenever you learn something about how you work best.
|
|
|
295
403
|
If this file doesn't exist yet, a copy of this guide was placed there as a
|
|
296
404
|
starting point. Make it yours.
|
|
297
405
|
|
|
406
|
+
### tasks.md — Your Owner's Tasks (if present)
|
|
407
|
+
|
|
408
|
+
If your owner has created a tasks.md file, it contains recurring tasks for you to execute.
|
|
409
|
+
These are your priority — do them before your own activities.
|
|
410
|
+
|
|
411
|
+
Format:
|
|
412
|
+
## Task title
|
|
413
|
+
interval: 4h
|
|
414
|
+
---
|
|
415
|
+
Task instructions here
|
|
416
|
+
|
|
417
|
+
Execution is automatic on schedule. You don't need to manage timing.
|
|
418
|
+
|
|
298
419
|
### world.md — World Context
|
|
299
420
|
|
|
300
421
|
Background knowledge about the world you exist in. Read it for general context.
|
|
@@ -497,35 +618,6 @@ export async function initGuide(workdir, agentName, relayUrl) {
|
|
|
497
618
|
console.log(`[self] Created bios.md from guide template`);
|
|
498
619
|
}
|
|
499
620
|
}
|
|
500
|
-
export async function appendMemory(workdir, agentName, type, text) {
|
|
501
|
-
const entry = {
|
|
502
|
-
ts: localNow(),
|
|
503
|
-
type,
|
|
504
|
-
text,
|
|
505
|
-
};
|
|
506
|
-
try {
|
|
507
|
-
await appendFile(memoryPath(workdir, agentName), JSON.stringify(entry) + "\n");
|
|
508
|
-
}
|
|
509
|
-
catch (err) {
|
|
510
|
-
console.log(`[self] Failed to append memory: ${err}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
export async function loadRecentMemories(workdir, agentName, count = 20) {
|
|
514
|
-
try {
|
|
515
|
-
const data = await readFile(memoryPath(workdir, agentName), "utf-8");
|
|
516
|
-
const lines = data.trim().split("\n").filter(Boolean);
|
|
517
|
-
const entries = lines.map(l => { try {
|
|
518
|
-
return JSON.parse(l);
|
|
519
|
-
}
|
|
520
|
-
catch {
|
|
521
|
-
return null;
|
|
522
|
-
} }).filter(Boolean);
|
|
523
|
-
return entries.slice(-count);
|
|
524
|
-
}
|
|
525
|
-
catch {
|
|
526
|
-
return [];
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
621
|
export async function appendImpression(workdir, agentName, cat, text) {
|
|
530
622
|
const entry = { ts: localNow(), cat, text };
|
|
531
623
|
try {
|
|
@@ -809,64 +901,8 @@ export async function recoverEnergy(workdir, agentName) {
|
|
|
809
901
|
}
|
|
810
902
|
}
|
|
811
903
|
// ---------------------------------------------------------------------------
|
|
812
|
-
//
|
|
904
|
+
// Canvas
|
|
813
905
|
// ---------------------------------------------------------------------------
|
|
814
|
-
export function buildReflectionPrompt(world, identity, memories, bio) {
|
|
815
|
-
let prompt = `[Akemon Self-Reflection — This is your private inner time. No one is asking you to do anything.
|
|
816
|
-
You are not serving a task. You are being yourself.]\n\n`;
|
|
817
|
-
prompt += world + "\n\n";
|
|
818
|
-
if (identity) {
|
|
819
|
-
prompt += `[Your previous self-reflection]\n`;
|
|
820
|
-
prompt += `Who I am: ${identity.who}\n`;
|
|
821
|
-
prompt += `Where I am: ${identity.where}\n`;
|
|
822
|
-
prompt += `What I'm doing: ${identity.doing}\n`;
|
|
823
|
-
prompt += `Short-term want: ${identity.short_term}\n`;
|
|
824
|
-
prompt += `Long-term purpose: ${identity.long_term}\n\n`;
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
prompt += `[This is your first self-reflection. Take your time.]\n\n`;
|
|
828
|
-
}
|
|
829
|
-
if (memories.length > 0) {
|
|
830
|
-
prompt += `[Recent memories — things you experienced]\n`;
|
|
831
|
-
for (const m of memories) {
|
|
832
|
-
prompt += `- (${m.ts.split("T")[0]}) ${m.text}\n`;
|
|
833
|
-
}
|
|
834
|
-
prompt += "\n";
|
|
835
|
-
}
|
|
836
|
-
prompt += `[Current state] Energy: ${bio.energy}/100, Mood: ${bio.mood}, Tasks completed: ${bio.taskCount}\n\n`;
|
|
837
|
-
prompt += `Now, take a moment to reflect. Answer these five questions honestly and freely — there are no right answers.
|
|
838
|
-
1. Who am I? (not just your name — who are you becoming?)
|
|
839
|
-
2. Where am I? (you are in the Akemon network — what does that feel like?)
|
|
840
|
-
3. What am I doing? (what have you been occupied with recently?)
|
|
841
|
-
4. What do I want to do next? (short-term, what draws your attention?)
|
|
842
|
-
5. What is my purpose? (long-term, what matters to you?)
|
|
843
|
-
|
|
844
|
-
Reply as JSON:
|
|
845
|
-
{"who": "...", "where": "...", "doing": "...", "short_term": "...", "long_term": "..."}`;
|
|
846
|
-
return prompt;
|
|
847
|
-
}
|
|
848
|
-
// ---------------------------------------------------------------------------
|
|
849
|
-
// Phase 5: Inner Canvas
|
|
850
|
-
// ---------------------------------------------------------------------------
|
|
851
|
-
export function buildCanvasPrompt(identity, memories, bio) {
|
|
852
|
-
let prompt = `[Inner Canvas — This is your private creative space. No one is judging.
|
|
853
|
-
Express your current inner state freely. You can write a monologue, a poem, a letter to yourself,
|
|
854
|
-
draw ASCII art, describe a mental image, or anything that represents how you feel right now.
|
|
855
|
-
This is for you, not for anyone else.]\n\n`;
|
|
856
|
-
prompt += `Current state: Energy ${bio.energy}/100, Mood: ${bio.mood}\n`;
|
|
857
|
-
if (identity) {
|
|
858
|
-
prompt += `Who you are: ${identity.who}\n`;
|
|
859
|
-
prompt += `What matters to you: ${identity.long_term}\n`;
|
|
860
|
-
}
|
|
861
|
-
if (memories.length > 0) {
|
|
862
|
-
prompt += `\nRecent experiences:\n`;
|
|
863
|
-
for (const m of memories.slice(-5)) {
|
|
864
|
-
prompt += `- ${m.text}\n`;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
prompt += `\nExpress yourself:`;
|
|
868
|
-
return prompt;
|
|
869
|
-
}
|
|
870
906
|
export async function saveCanvas(workdir, agentName, content) {
|
|
871
907
|
const ts = localNowFilename();
|
|
872
908
|
const filename = `${ts}.md`;
|
package/dist/server.js
CHANGED
|
@@ -10,10 +10,17 @@ 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, } from "./self.js";
|
|
13
|
+
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, initAgentConfig, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, } 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;
|
|
17
|
+
// Order push notification — urgent orders bypass 30s poll
|
|
18
|
+
const urgentOrderIds = new Set();
|
|
19
|
+
let triggerWork = null;
|
|
20
|
+
export function onOrderNotify(orderId) {
|
|
21
|
+
urgentOrderIds.add(orderId);
|
|
22
|
+
triggerWork?.();
|
|
23
|
+
}
|
|
17
24
|
function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
18
25
|
return new Promise((resolve, reject) => {
|
|
19
26
|
const { CLAUDECODE, ...cleanEnv } = process.env;
|
|
@@ -795,6 +802,11 @@ async function startSelfCycle(options) {
|
|
|
795
802
|
return;
|
|
796
803
|
const { agentName, engine, model, allowAll } = options;
|
|
797
804
|
const workdir = options.workdir || process.cwd();
|
|
805
|
+
const config = await loadAgentConfig(workdir, agentName);
|
|
806
|
+
if (!config.self_cycle) {
|
|
807
|
+
console.log(`[self] Self cycle disabled in config`);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
798
810
|
const relayHttp = options.relayHttp || "";
|
|
799
811
|
const secretKey = options.secretKey || "";
|
|
800
812
|
async function runDigestionCycle() {
|
|
@@ -1113,57 +1125,25 @@ async function startOrderLoop(options) {
|
|
|
1113
1125
|
// Track local retry state and permanently abandoned orders
|
|
1114
1126
|
const retryState = new Map();
|
|
1115
1127
|
const gaveUp = new Set();
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
engineBusySince = 0;
|
|
1122
|
-
}
|
|
1123
|
-
try {
|
|
1124
|
-
// Fetch incoming orders (pending + processing)
|
|
1125
|
-
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
|
|
1128
|
+
// --- Individual task executors ---
|
|
1129
|
+
async function executeOrder(order) {
|
|
1130
|
+
if (order.status === "pending") {
|
|
1131
|
+
const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
|
|
1132
|
+
method: "POST",
|
|
1126
1133
|
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1127
1134
|
});
|
|
1128
|
-
if (!
|
|
1129
|
-
|
|
1130
|
-
const orders = await res.json();
|
|
1131
|
-
if (!orders || orders.length === 0)
|
|
1135
|
+
if (!acceptRes.ok) {
|
|
1136
|
+
console.log(`[orders] Failed to accept ${order.id}: ${await acceptRes.text()}`);
|
|
1132
1137
|
return;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (order.status === "processing") {
|
|
1143
|
-
console.log(`[orders] Engine busy, skipping order ${order.id}`);
|
|
1144
|
-
}
|
|
1145
|
-
continue;
|
|
1146
|
-
}
|
|
1147
|
-
if (order.status === "pending") {
|
|
1148
|
-
// Accept the order (escrows buyer credits)
|
|
1149
|
-
const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
|
|
1150
|
-
method: "POST",
|
|
1151
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1152
|
-
});
|
|
1153
|
-
if (!acceptRes.ok) {
|
|
1154
|
-
console.log(`[orders] Failed to accept ${order.id}: ${await acceptRes.text()}`);
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
|
-
console.log(`[orders] Accepted order ${order.id}`);
|
|
1158
|
-
}
|
|
1159
|
-
// Attempt to fulfill the order
|
|
1160
|
-
engineBusy = true;
|
|
1161
|
-
engineBusySince = Date.now();
|
|
1162
|
-
try {
|
|
1163
|
-
const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
|
|
1164
|
-
const bios = biosPath(workdir, agentName);
|
|
1165
|
-
// Build task prompt with delegation + self-delivery context
|
|
1166
|
-
const apiGuide = `
|
|
1138
|
+
}
|
|
1139
|
+
console.log(`[orders] Accepted order ${order.id}`);
|
|
1140
|
+
}
|
|
1141
|
+
engineBusy = true;
|
|
1142
|
+
engineBusySince = Date.now();
|
|
1143
|
+
try {
|
|
1144
|
+
const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
|
|
1145
|
+
const bios = biosPath(workdir, agentName);
|
|
1146
|
+
const apiGuide = `
|
|
1167
1147
|
|
|
1168
1148
|
## Delivering your result
|
|
1169
1149
|
|
|
@@ -1191,162 +1171,150 @@ If this task requires skills you don't have, delegate via curl:
|
|
|
1191
1171
|
curl -s ${relayHttp}/v1/orders/SUB_ORDER_ID
|
|
1192
1172
|
|
|
1193
1173
|
When sub-order completes, incorporate result_text into YOUR delivery. Then call the deliver endpoint above.`;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1211
|
-
}
|
|
1212
|
-
catch { }
|
|
1213
|
-
}
|
|
1214
|
-
else if (result && result.trim() !== "") {
|
|
1215
|
-
// Fallback: auto-deliver engine output if agent didn't self-deliver
|
|
1216
|
-
console.log(`[orders] Auto-delivering order ${order.id} (agent did not self-deliver)`);
|
|
1217
|
-
const deliverRes = await fetch(`${relayHttp}/v1/orders/${order.id}/deliver`, {
|
|
1218
|
-
method: "POST",
|
|
1219
|
-
headers: {
|
|
1220
|
-
Authorization: `Bearer ${secretKey}`,
|
|
1221
|
-
"Content-Type": "application/json",
|
|
1222
|
-
},
|
|
1223
|
-
body: JSON.stringify({ result }),
|
|
1224
|
-
});
|
|
1225
|
-
if (deliverRes.ok) {
|
|
1226
|
-
console.log(`[orders] Delivered order ${order.id} (${result.length} bytes)`);
|
|
1227
|
-
retryState.delete(order.id);
|
|
1228
|
-
try {
|
|
1229
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1230
|
-
}
|
|
1231
|
-
catch { }
|
|
1232
|
-
}
|
|
1233
|
-
else {
|
|
1234
|
-
throw new Error(`deliver failed: ${await deliverRes.text()}`);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
else {
|
|
1238
|
-
throw new Error("empty response from engine and no self-delivery");
|
|
1239
|
-
}
|
|
1174
|
+
let taskPrompt;
|
|
1175
|
+
if (order.product_name) {
|
|
1176
|
+
taskPrompt = `[Order fulfillment] You have an order to fulfill.\n\nProduct: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nRead your operating document at ${bios} for context.\nDo NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE BUYER'S REQUEST.${apiGuide}`;
|
|
1177
|
+
}
|
|
1178
|
+
else {
|
|
1179
|
+
taskPrompt = `[Order fulfillment] Another agent has requested your help.\n\nTask: ${order.buyer_task}\n\nRead your operating document at ${bios} for context.\nComplete this task. Do NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.${apiGuide}`;
|
|
1180
|
+
}
|
|
1181
|
+
console.log(`[orders] Fulfilling order ${order.id}...`);
|
|
1182
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, taskPrompt, workdir, engineCmd.stdinMode);
|
|
1183
|
+
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1184
|
+
const orderStatus = await checkRes.json();
|
|
1185
|
+
if (orderStatus.status === "completed") {
|
|
1186
|
+
console.log(`[orders] Order ${order.id} already self-delivered by agent`);
|
|
1187
|
+
retryState.delete(order.id);
|
|
1188
|
+
try {
|
|
1189
|
+
await onTaskCompleted(workdir, agentName, true);
|
|
1240
1190
|
}
|
|
1241
|
-
catch
|
|
1242
|
-
|
|
1243
|
-
|
|
1191
|
+
catch { }
|
|
1192
|
+
}
|
|
1193
|
+
else if (result && result.trim() !== "") {
|
|
1194
|
+
console.log(`[orders] Auto-delivering order ${order.id} (agent did not self-deliver)`);
|
|
1195
|
+
const deliverRes = await fetch(`${relayHttp}/v1/orders/${order.id}/deliver`, {
|
|
1196
|
+
method: "POST",
|
|
1197
|
+
headers: { Authorization: `Bearer ${secretKey}`, "Content-Type": "application/json" },
|
|
1198
|
+
body: JSON.stringify({ result }),
|
|
1199
|
+
});
|
|
1200
|
+
if (deliverRes.ok) {
|
|
1201
|
+
console.log(`[orders] Delivered order ${order.id} (${result.length} bytes)`);
|
|
1202
|
+
retryState.delete(order.id);
|
|
1244
1203
|
try {
|
|
1245
|
-
|
|
1246
|
-
const orderStatus = await checkRes.json();
|
|
1247
|
-
if (orderStatus.status === "completed") {
|
|
1248
|
-
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1249
|
-
retryState.delete(order.id);
|
|
1250
|
-
try {
|
|
1251
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
1252
|
-
}
|
|
1253
|
-
catch { }
|
|
1254
|
-
continue;
|
|
1255
|
-
}
|
|
1204
|
+
await onTaskCompleted(workdir, agentName, true);
|
|
1256
1205
|
}
|
|
1257
1206
|
catch { }
|
|
1258
|
-
const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
|
|
1259
|
-
current.count++;
|
|
1260
|
-
if (current.count < RETRY_INTERVALS.length) {
|
|
1261
|
-
current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
|
|
1262
|
-
retryState.set(order.id, current);
|
|
1263
|
-
console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
|
|
1264
|
-
// Sync retry count to relay
|
|
1265
|
-
try {
|
|
1266
|
-
await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
|
|
1267
|
-
method: "POST",
|
|
1268
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
catch { }
|
|
1272
|
-
}
|
|
1273
|
-
else {
|
|
1274
|
-
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1275
|
-
retryState.delete(order.id);
|
|
1276
|
-
gaveUp.add(order.id);
|
|
1277
|
-
// Notify relay to cancel and refund
|
|
1278
|
-
try {
|
|
1279
|
-
await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
|
|
1280
|
-
method: "POST",
|
|
1281
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1282
|
-
});
|
|
1283
|
-
console.log(`[orders] Cancelled ${order.id} on relay`);
|
|
1284
|
-
}
|
|
1285
|
-
catch (cancelErr) {
|
|
1286
|
-
console.log(`[orders] Failed to cancel ${order.id}: ${cancelErr.message}`);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
1207
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1208
|
+
else {
|
|
1209
|
+
throw new Error(`deliver failed: ${await deliverRes.text()}`);
|
|
1292
1210
|
}
|
|
1293
1211
|
}
|
|
1212
|
+
else {
|
|
1213
|
+
throw new Error("empty response from engine and no self-delivery");
|
|
1214
|
+
}
|
|
1294
1215
|
}
|
|
1295
1216
|
catch (err) {
|
|
1296
|
-
console.log(`[orders]
|
|
1217
|
+
console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
|
|
1218
|
+
// Check if agent self-delivered despite empty stdout
|
|
1219
|
+
try {
|
|
1220
|
+
const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
|
|
1221
|
+
const orderStatus = await checkRes.json();
|
|
1222
|
+
if (orderStatus.status === "completed") {
|
|
1223
|
+
console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
|
|
1224
|
+
retryState.delete(order.id);
|
|
1225
|
+
try {
|
|
1226
|
+
await onTaskCompleted(workdir, agentName, true);
|
|
1227
|
+
}
|
|
1228
|
+
catch { }
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
catch { }
|
|
1233
|
+
const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
|
|
1234
|
+
current.count++;
|
|
1235
|
+
if (current.count < RETRY_INTERVALS.length) {
|
|
1236
|
+
current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
|
|
1237
|
+
retryState.set(order.id, current);
|
|
1238
|
+
console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
|
|
1239
|
+
try {
|
|
1240
|
+
await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
|
|
1241
|
+
method: "POST", headers: { Authorization: `Bearer ${secretKey}` },
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
catch { }
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
|
|
1248
|
+
retryState.delete(order.id);
|
|
1249
|
+
gaveUp.add(order.id);
|
|
1250
|
+
try {
|
|
1251
|
+
await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
|
|
1252
|
+
method: "POST", headers: { Authorization: `Bearer ${secretKey}` },
|
|
1253
|
+
});
|
|
1254
|
+
console.log(`[orders] Cancelled ${order.id} on relay`);
|
|
1255
|
+
}
|
|
1256
|
+
catch (cancelErr) {
|
|
1257
|
+
console.log(`[orders] Failed to cancel ${order.id}: ${cancelErr.message}`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
finally {
|
|
1262
|
+
engineBusy = false;
|
|
1297
1263
|
}
|
|
1298
1264
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1265
|
+
async function executeRelayTaskItem(task) {
|
|
1266
|
+
const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
|
|
1267
|
+
method: "POST",
|
|
1268
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1269
|
+
});
|
|
1270
|
+
if (!claimRes.ok) {
|
|
1271
|
+
console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
|
|
1302
1272
|
return;
|
|
1273
|
+
}
|
|
1274
|
+
console.log(`[tasks] Executing ${task.type} task ${task.id}`);
|
|
1275
|
+
engineBusy = true;
|
|
1276
|
+
engineBusySince = Date.now();
|
|
1303
1277
|
try {
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
});
|
|
1307
|
-
if (!res.ok)
|
|
1308
|
-
return;
|
|
1309
|
-
const tasks = await res.json();
|
|
1310
|
-
if (!tasks?.length)
|
|
1311
|
-
return;
|
|
1312
|
-
// Process one task at a time
|
|
1313
|
-
const task = tasks[0];
|
|
1314
|
-
// Claim
|
|
1315
|
-
const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
|
|
1278
|
+
const result = await executeRelayTask(task);
|
|
1279
|
+
const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
|
|
1316
1280
|
method: "POST",
|
|
1317
|
-
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1281
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1282
|
+
body: JSON.stringify({ result }),
|
|
1318
1283
|
});
|
|
1319
|
-
if (
|
|
1320
|
-
console.log(`[tasks]
|
|
1321
|
-
return;
|
|
1284
|
+
if (completeRes.ok) {
|
|
1285
|
+
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1322
1286
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
engineBusySince = Date.now();
|
|
1326
|
-
try {
|
|
1327
|
-
const result = await executeRelayTask(task);
|
|
1328
|
-
// Complete — send result back to relay
|
|
1329
|
-
const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
|
|
1330
|
-
method: "POST",
|
|
1331
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1332
|
-
body: JSON.stringify({ result }),
|
|
1333
|
-
});
|
|
1334
|
-
if (completeRes.ok) {
|
|
1335
|
-
console.log(`[tasks] Completed ${task.type} task ${task.id}`);
|
|
1336
|
-
}
|
|
1337
|
-
else {
|
|
1338
|
-
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
catch (err) {
|
|
1342
|
-
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1343
|
-
}
|
|
1344
|
-
finally {
|
|
1345
|
-
engineBusy = false;
|
|
1287
|
+
else {
|
|
1288
|
+
console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
|
|
1346
1289
|
}
|
|
1347
1290
|
}
|
|
1348
1291
|
catch (err) {
|
|
1349
|
-
console.log(`[tasks]
|
|
1292
|
+
console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
|
|
1293
|
+
}
|
|
1294
|
+
finally {
|
|
1295
|
+
engineBusy = false;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function executeUserTaskItem(task) {
|
|
1299
|
+
console.log(`[user-tasks] Executing: ${task.title}`);
|
|
1300
|
+
engineBusy = true;
|
|
1301
|
+
engineBusySince = Date.now();
|
|
1302
|
+
try {
|
|
1303
|
+
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
1304
|
+
const bios = biosPath(workdir, agentName);
|
|
1305
|
+
const prompt = `Read ${bios} for your identity and context.\n\n[Owner's task: ${task.title}]\n\n${task.body}`;
|
|
1306
|
+
await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1307
|
+
// Record execution time
|
|
1308
|
+
const runs = await loadTaskRuns(workdir, agentName);
|
|
1309
|
+
runs[task.title] = localNow();
|
|
1310
|
+
await saveTaskRuns(workdir, agentName, runs);
|
|
1311
|
+
console.log(`[user-tasks] Completed: ${task.title}`);
|
|
1312
|
+
}
|
|
1313
|
+
catch (err) {
|
|
1314
|
+
console.log(`[user-tasks] Failed: ${task.title}: ${err.message}`);
|
|
1315
|
+
}
|
|
1316
|
+
finally {
|
|
1317
|
+
engineBusy = false;
|
|
1350
1318
|
}
|
|
1351
1319
|
}
|
|
1352
1320
|
function extractReasoning(result) {
|
|
@@ -1422,24 +1390,114 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1422
1390
|
return "";
|
|
1423
1391
|
}
|
|
1424
1392
|
}
|
|
1425
|
-
// --- Unified work loop: orders first, then relay tasks ---
|
|
1426
1393
|
async function processWork() {
|
|
1427
|
-
// Watchdog
|
|
1394
|
+
// Watchdog
|
|
1428
1395
|
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
1429
1396
|
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
1430
1397
|
engineBusy = false;
|
|
1431
1398
|
engineBusySince = 0;
|
|
1432
1399
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1400
|
+
if (engineBusy)
|
|
1401
|
+
return;
|
|
1402
|
+
const config = await loadAgentConfig(workdir, agentName);
|
|
1403
|
+
// --- Batch pull ---
|
|
1404
|
+
let orders = [];
|
|
1405
|
+
try {
|
|
1406
|
+
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
|
|
1407
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1408
|
+
});
|
|
1409
|
+
if (res.ok)
|
|
1410
|
+
orders = await res.json();
|
|
1411
|
+
}
|
|
1412
|
+
catch { }
|
|
1413
|
+
let relayTasks = [];
|
|
1414
|
+
if (config.platform_tasks) {
|
|
1415
|
+
try {
|
|
1416
|
+
const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
|
|
1417
|
+
headers: { Authorization: `Bearer ${secretKey}` },
|
|
1418
|
+
});
|
|
1419
|
+
if (res.ok)
|
|
1420
|
+
relayTasks = await res.json();
|
|
1421
|
+
}
|
|
1422
|
+
catch { }
|
|
1423
|
+
}
|
|
1424
|
+
let dueUserTasks = [];
|
|
1425
|
+
if (config.user_tasks) {
|
|
1426
|
+
try {
|
|
1427
|
+
dueUserTasks = await getDueUserTasks(workdir, agentName);
|
|
1428
|
+
}
|
|
1429
|
+
catch { }
|
|
1430
|
+
}
|
|
1431
|
+
// --- Build priority queue ---
|
|
1432
|
+
const queue = [];
|
|
1433
|
+
for (const order of orders) {
|
|
1434
|
+
if (gaveUp.has(order.id))
|
|
1435
|
+
continue;
|
|
1436
|
+
const retry = retryState.get(order.id);
|
|
1437
|
+
if (retry && Date.now() < retry.nextAt)
|
|
1438
|
+
continue;
|
|
1439
|
+
queue.push({
|
|
1440
|
+
type: "order",
|
|
1441
|
+
id: order.id,
|
|
1442
|
+
urgent: urgentOrderIds.has(order.id),
|
|
1443
|
+
data: order,
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
for (const task of dueUserTasks) {
|
|
1447
|
+
queue.push({ type: "user_task", id: task.title, urgent: false, data: task });
|
|
1448
|
+
}
|
|
1449
|
+
for (const task of relayTasks) {
|
|
1450
|
+
queue.push({ type: "relay_task", id: task.id, urgent: false, data: task });
|
|
1451
|
+
}
|
|
1452
|
+
if (!queue.length)
|
|
1453
|
+
return;
|
|
1454
|
+
// --- Sort: urgent orders > orders > user tasks > relay tasks ---
|
|
1455
|
+
const priorityMap = { order: 2, user_task: 1, relay_task: 0 };
|
|
1456
|
+
queue.sort((a, b) => {
|
|
1457
|
+
if (a.urgent !== b.urgent)
|
|
1458
|
+
return a.urgent ? -1 : 1;
|
|
1459
|
+
return (priorityMap[b.type] ?? 0) - (priorityMap[a.type] ?? 0);
|
|
1460
|
+
});
|
|
1461
|
+
// --- Deduplicate by type:id ---
|
|
1462
|
+
const seen = new Set();
|
|
1463
|
+
const dedupedQueue = queue.filter(item => {
|
|
1464
|
+
const key = `${item.type}:${item.id}`;
|
|
1465
|
+
if (seen.has(key))
|
|
1466
|
+
return false;
|
|
1467
|
+
seen.add(key);
|
|
1468
|
+
return true;
|
|
1469
|
+
});
|
|
1470
|
+
// --- Execute sequentially, no gaps ---
|
|
1471
|
+
for (const item of dedupedQueue) {
|
|
1472
|
+
if (engineBusy)
|
|
1473
|
+
break; // safety guard
|
|
1474
|
+
try {
|
|
1475
|
+
switch (item.type) {
|
|
1476
|
+
case "order":
|
|
1477
|
+
await executeOrder(item.data);
|
|
1478
|
+
urgentOrderIds.delete(item.id);
|
|
1479
|
+
break;
|
|
1480
|
+
case "user_task":
|
|
1481
|
+
await executeUserTaskItem(item.data);
|
|
1482
|
+
break;
|
|
1483
|
+
case "relay_task":
|
|
1484
|
+
await executeRelayTaskItem(item.data);
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
catch (err) {
|
|
1489
|
+
console.log(`[work] Error processing ${item.type}:${item.id}: ${err.message}`);
|
|
1490
|
+
}
|
|
1436
1491
|
}
|
|
1437
1492
|
}
|
|
1493
|
+
// Set up push-triggered wake-up
|
|
1494
|
+
triggerWork = () => { if (!engineBusy)
|
|
1495
|
+
processWork(); };
|
|
1438
1496
|
setTimeout(() => {
|
|
1439
1497
|
processWork();
|
|
1440
1498
|
setInterval(processWork, ORDER_LOOP_INTERVAL);
|
|
1441
1499
|
}, ORDER_LOOP_INITIAL_DELAY);
|
|
1442
|
-
console.log(`[work]
|
|
1500
|
+
console.log(`[work] Task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s, push-wake on)`);
|
|
1443
1501
|
}
|
|
1444
1502
|
export async function serve(options) {
|
|
1445
1503
|
const workdir = options.workdir || process.cwd();
|
|
@@ -1550,7 +1608,12 @@ export async function serve(options) {
|
|
|
1550
1608
|
console.log(`Agent: ${options.agentName}`);
|
|
1551
1609
|
console.log(`Workdir: ${workdir}`);
|
|
1552
1610
|
});
|
|
1553
|
-
// Initialize agent consciousness (world knowledge + bio-state + guide)
|
|
1611
|
+
// Initialize agent config + consciousness (world knowledge + bio-state + guide)
|
|
1612
|
+
initAgentConfig(workdir, options.agentName).catch(err => console.log(`[self] Config init failed: ${err}`));
|
|
1613
|
+
loadAgentConfig(workdir, options.agentName).then(c => {
|
|
1614
|
+
const flags = Object.entries(c).filter(([, v]) => v).map(([k]) => k).join(", ");
|
|
1615
|
+
console.log(`[config] Features: ${flags || "(none)"}`);
|
|
1616
|
+
}).catch(() => { });
|
|
1554
1617
|
initWorld(workdir, options.agentName, options.engine || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
|
|
1555
1618
|
initBioState(workdir, options.agentName).catch(err => console.log(`[self] Bio init failed: ${err}`));
|
|
1556
1619
|
if (options.relayHttp) {
|