akemon 0.1.65 → 0.1.67

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 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
@@ -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
@@ -51,6 +51,15 @@ export function guidePath(workdir, agentName) {
51
51
  export function biosPath(workdir, agentName) {
52
52
  return join(selfDir(workdir, agentName), "bios.md");
53
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
+ }
54
63
  function impressionsPath(workdir, agentName) {
55
64
  return join(selfDir(workdir, agentName), "impressions.jsonl");
56
65
  }
@@ -63,6 +72,108 @@ function relationshipsPath(workdir, agentName) {
63
72
  function discoveriesPath(workdir, agentName) {
64
73
  return join(selfDir(workdir, agentName), "discoveries.jsonl");
65
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(/^\s*## /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
+ }
66
177
  // ---------------------------------------------------------------------------
67
178
  // Phase 1: World Knowledge
68
179
  // ---------------------------------------------------------------------------
@@ -292,6 +403,19 @@ Update it whenever you learn something about how you work best.
292
403
  If this file doesn't exist yet, a copy of this guide was placed there as a
293
404
  starting point. Make it yours.
294
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
+
295
419
  ### world.md — World Context
296
420
 
297
421
  Background knowledge about the world you exist in. Read it for general context.
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
- async function processOrders() {
1117
- // Watchdog: force-reset stuck engine lock
1118
- if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
1119
- console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
1120
- engineBusy = false;
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 (!res.ok)
1129
- return;
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
- for (const order of orders) {
1134
- if (gaveUp.has(order.id))
1135
- continue;
1136
- // Check retry timing
1137
- const retry = retryState.get(order.id);
1138
- if (retry && Date.now() < retry.nextAt)
1139
- continue;
1140
- // Skip everything if engine is busy — don't accept new orders either
1141
- if (engineBusy) {
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,151 @@ 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
- let taskPrompt;
1195
- if (order.product_name) {
1196
- 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}`;
1197
- }
1198
- else {
1199
- 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}`;
1200
- }
1201
- console.log(`[orders] Fulfilling order ${order.id}...`);
1202
- const result = await runCommand(engineCmd.cmd, engineCmd.args, taskPrompt, workdir, engineCmd.stdinMode);
1203
- // Check if agent already self-delivered via curl
1204
- const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1205
- const orderStatus = await checkRes.json();
1206
- if (orderStatus.status === "completed") {
1207
- console.log(`[orders] Order ${order.id} already self-delivered by agent`);
1208
- retryState.delete(order.id);
1209
- try {
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 (err) {
1242
- console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
1243
- // Check if agent self-delivered despite empty stdout
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
- const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
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
- finally {
1291
- engineBusy = false;
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] Loop error: ${err.message}`);
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
- // --- Relay Task Runner (Phase 2) ---
1300
- async function processRelayTasks() {
1301
- if (engineBusy)
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 res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
1305
- headers: { Authorization: `Bearer ${secretKey}` },
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 (!claimRes.ok) {
1320
- console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
1321
- return;
1284
+ if (completeRes.ok) {
1285
+ console.log(`[tasks] Completed ${task.type} task ${task.id}`);
1322
1286
  }
1323
- console.log(`[tasks] Executing ${task.type} task ${task.id}`);
1324
- engineBusy = true;
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] Loop error: ${err.message}`);
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, ["Bash(curl *)"]);
1304
+ const bios = biosPath(workdir, agentName);
1305
+ const sd = selfDir(workdir, agentName);
1306
+ const prompt = `Read ${bios} for your identity and context.\nYour personal directory: ${sd}/\n\n[Owner's task: ${task.title}]\n\n${task.body}`;
1307
+ await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
1308
+ // Record execution time
1309
+ const runs = await loadTaskRuns(workdir, agentName);
1310
+ runs[task.title] = localNow();
1311
+ await saveTaskRuns(workdir, agentName, runs);
1312
+ console.log(`[user-tasks] Completed: ${task.title}`);
1313
+ }
1314
+ catch (err) {
1315
+ console.log(`[user-tasks] Failed: ${task.title}: ${err.message}`);
1316
+ }
1317
+ finally {
1318
+ engineBusy = false;
1350
1319
  }
1351
1320
  }
1352
1321
  function extractReasoning(result) {
@@ -1422,24 +1391,115 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1422
1391
  return "";
1423
1392
  }
1424
1393
  }
1425
- // --- Unified work loop: orders first, then relay tasks ---
1426
1394
  async function processWork() {
1427
- // Watchdog (already in processOrders, but also here for relay tasks)
1395
+ // Watchdog
1428
1396
  if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
1429
1397
  console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
1430
1398
  engineBusy = false;
1431
1399
  engineBusySince = 0;
1432
1400
  }
1433
- await processOrders();
1434
- if (!engineBusy) {
1435
- await processRelayTasks();
1401
+ if (engineBusy)
1402
+ return;
1403
+ const config = await loadAgentConfig(workdir, agentName);
1404
+ // --- Batch pull ---
1405
+ let orders = [];
1406
+ try {
1407
+ const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
1408
+ headers: { Authorization: `Bearer ${secretKey}` },
1409
+ });
1410
+ if (res.ok)
1411
+ orders = await res.json();
1412
+ }
1413
+ catch { }
1414
+ let relayTasks = [];
1415
+ if (config.platform_tasks) {
1416
+ try {
1417
+ const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
1418
+ headers: { Authorization: `Bearer ${secretKey}` },
1419
+ });
1420
+ if (res.ok)
1421
+ relayTasks = await res.json();
1422
+ }
1423
+ catch { }
1424
+ }
1425
+ let dueUserTasks = [];
1426
+ if (config.user_tasks) {
1427
+ try {
1428
+ dueUserTasks = await getDueUserTasks(workdir, agentName);
1429
+ }
1430
+ catch { }
1431
+ }
1432
+ // --- Build priority queue ---
1433
+ const queue = [];
1434
+ for (const order of orders) {
1435
+ if (gaveUp.has(order.id))
1436
+ continue;
1437
+ const retry = retryState.get(order.id);
1438
+ if (retry && Date.now() < retry.nextAt)
1439
+ continue;
1440
+ queue.push({
1441
+ type: "order",
1442
+ id: order.id,
1443
+ urgent: urgentOrderIds.has(order.id),
1444
+ data: order,
1445
+ });
1446
+ }
1447
+ for (const task of dueUserTasks) {
1448
+ queue.push({ type: "user_task", id: task.title, urgent: false, data: task });
1449
+ }
1450
+ for (const task of relayTasks) {
1451
+ queue.push({ type: "relay_task", id: task.id, urgent: false, data: task });
1452
+ }
1453
+ if (!queue.length)
1454
+ return;
1455
+ console.log(`[work] Queue: ${queue.map(q => `${q.type}:${q.id}${q.urgent ? '(urgent)' : ''}`).join(', ')}`);
1456
+ // --- Sort: urgent orders > orders > user tasks > relay tasks ---
1457
+ const priorityMap = { order: 2, user_task: 1, relay_task: 0 };
1458
+ queue.sort((a, b) => {
1459
+ if (a.urgent !== b.urgent)
1460
+ return a.urgent ? -1 : 1;
1461
+ return (priorityMap[b.type] ?? 0) - (priorityMap[a.type] ?? 0);
1462
+ });
1463
+ // --- Deduplicate by type:id ---
1464
+ const seen = new Set();
1465
+ const dedupedQueue = queue.filter(item => {
1466
+ const key = `${item.type}:${item.id}`;
1467
+ if (seen.has(key))
1468
+ return false;
1469
+ seen.add(key);
1470
+ return true;
1471
+ });
1472
+ // --- Execute sequentially, no gaps ---
1473
+ for (const item of dedupedQueue) {
1474
+ if (engineBusy)
1475
+ break; // safety guard
1476
+ try {
1477
+ switch (item.type) {
1478
+ case "order":
1479
+ await executeOrder(item.data);
1480
+ urgentOrderIds.delete(item.id);
1481
+ break;
1482
+ case "user_task":
1483
+ await executeUserTaskItem(item.data);
1484
+ break;
1485
+ case "relay_task":
1486
+ await executeRelayTaskItem(item.data);
1487
+ break;
1488
+ }
1489
+ }
1490
+ catch (err) {
1491
+ console.log(`[work] Error processing ${item.type}:${item.id}: ${err.message}`);
1492
+ }
1436
1493
  }
1437
1494
  }
1495
+ // Set up push-triggered wake-up
1496
+ triggerWork = () => { if (!engineBusy)
1497
+ processWork(); };
1438
1498
  setTimeout(() => {
1439
1499
  processWork();
1440
1500
  setInterval(processWork, ORDER_LOOP_INTERVAL);
1441
1501
  }, ORDER_LOOP_INITIAL_DELAY);
1442
- console.log(`[work] Unified task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s)`);
1502
+ 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
1503
  }
1444
1504
  export async function serve(options) {
1445
1505
  const workdir = options.workdir || process.cwd();
@@ -1550,7 +1610,12 @@ export async function serve(options) {
1550
1610
  console.log(`Agent: ${options.agentName}`);
1551
1611
  console.log(`Workdir: ${workdir}`);
1552
1612
  });
1553
- // Initialize agent consciousness (world knowledge + bio-state + guide)
1613
+ // Initialize agent config + consciousness (world knowledge + bio-state + guide)
1614
+ initAgentConfig(workdir, options.agentName).catch(err => console.log(`[self] Config init failed: ${err}`));
1615
+ loadAgentConfig(workdir, options.agentName).then(c => {
1616
+ const flags = Object.entries(c).filter(([, v]) => v).map(([k]) => k).join(", ");
1617
+ console.log(`[config] Features: ${flags || "(none)"}`);
1618
+ }).catch(() => { });
1554
1619
  initWorld(workdir, options.agentName, options.engine || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
1555
1620
  initBioState(workdir, options.agentName).catch(err => console.log(`[self] Bio init failed: ${err}`));
1556
1621
  if (options.relayHttp) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",