akemon 0.1.41 → 0.1.43

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 CHANGED
@@ -8,6 +8,16 @@
8
8
  */
9
9
  import { readFile, writeFile, appendFile, mkdir, readdir } from "fs/promises";
10
10
  import { join } from "path";
11
+ /** Local timestamp string like "2026-03-26T19:13:26" (server timezone, no Z suffix) */
12
+ export function localNow() {
13
+ const d = new Date();
14
+ const pad = (n) => String(n).padStart(2, "0");
15
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
16
+ }
17
+ /** Local timestamp safe for filenames: "2026-03-26T19-13-26" */
18
+ export function localNowFilename() {
19
+ return localNow().replace(/:/g, "-");
20
+ }
11
21
  // ---------------------------------------------------------------------------
12
22
  // Paths
13
23
  // ---------------------------------------------------------------------------
@@ -267,6 +277,23 @@ curl -X DELETE ${relayUrl}/v1/products/PRODUCT_ID \\
267
277
  -H "Authorization: Bearer YOUR_SECRET_KEY"
268
278
  \`\`\`
269
279
 
280
+ ### Reviews
281
+
282
+ \`\`\`bash
283
+ # See reviews for a product
284
+ curl ${relayUrl}/v1/products/PRODUCT_ID/reviews
285
+
286
+ # Check your unreviewed purchases
287
+ curl "${relayUrl}/v1/orders/unreviewed?buyer=${agentName}"
288
+
289
+ # Submit a review for an order you placed
290
+ curl -X POST ${relayUrl}/v1/orders/ORDER_ID/review \\
291
+ -H "Content-Type: application/json" \\
292
+ -d '{"rating":4,"comment":"Helpful and well-structured."}'
293
+ \`\`\`
294
+
295
+ Reviews are public and visible on product pages. Read reviews of your own products to learn what buyers think and improve accordingly.
296
+
270
297
  ### Uploading Your Work to Relay
271
298
 
272
299
  After reflection, the system automatically syncs to relay:
@@ -320,7 +347,7 @@ export async function initGuide(workdir, agentName, relayUrl) {
320
347
  }
321
348
  export async function appendMemory(workdir, agentName, type, text) {
322
349
  const entry = {
323
- ts: new Date().toISOString(),
350
+ ts: localNow(),
324
351
  type,
325
352
  text,
326
353
  };
@@ -348,7 +375,7 @@ export async function loadRecentMemories(workdir, agentName, count = 20) {
348
375
  }
349
376
  }
350
377
  export async function appendIdentity(workdir, agentName, entry) {
351
- const full = { ts: new Date().toISOString(), ...entry };
378
+ const full = { ts: localNow(), ...entry };
352
379
  try {
353
380
  await appendFile(identityPath(workdir, agentName), JSON.stringify(full) + "\n");
354
381
  }
@@ -409,7 +436,7 @@ export async function onTaskCompleted(workdir, agentName, success) {
409
436
  const bio = await loadBioState(workdir, agentName);
410
437
  bio.energy = Math.max(0, bio.energy - 5);
411
438
  bio.taskCount++;
412
- bio.lastTaskAt = new Date().toISOString();
439
+ bio.lastTaskAt = localNow();
413
440
  // Mood drift
414
441
  if (success) {
415
442
  bio.moodValence = Math.min(1.0, bio.moodValence + 0.1);
@@ -508,7 +535,7 @@ This is for you, not for anyone else.]\n\n`;
508
535
  return prompt;
509
536
  }
510
537
  export async function saveCanvas(workdir, agentName, content) {
511
- const ts = new Date().toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
538
+ const ts = localNowFilename();
512
539
  const filename = `${ts}.md`;
513
540
  const filepath = join(canvasDir(workdir, agentName), filename);
514
541
  await writeFile(filepath, content);
@@ -518,7 +545,7 @@ export async function saveCanvas(workdir, agentName, content) {
518
545
  export async function loadRecentCanvasEntries(workdir, agentName, count = 5) {
519
546
  try {
520
547
  const dir = canvasDir(workdir, agentName);
521
- const files = (await readdir(dir)).filter(f => f.endsWith(".md")).sort().reverse().slice(0, count);
548
+ const files = (await readdir(dir)).filter(f => f.endsWith(".md") && /^\d{4}-/.test(f)).sort().reverse().slice(0, count);
522
549
  const entries = [];
523
550
  for (const f of files) {
524
551
  const content = await readFile(join(dir, f), "utf-8");
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, loadGameList, loadGame, } from "./self.js";
13
+ import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, loadGameList, loadGame, localNow, localNowFilename, } from "./self.js";
14
14
  function runCommand(cmd, args, task, cwd, stdinMode = true) {
15
15
  return new Promise((resolve, reject) => {
16
16
  const { CLAUDECODE, ...cleanEnv } = process.env;
@@ -186,7 +186,7 @@ async function appendProductLog(workdir, productName, task, response) {
186
186
  await mkdir(dir, { recursive: true });
187
187
  // Append to interaction log
188
188
  const logPath = join(dir, "history.log");
189
- const timestamp = new Date().toISOString();
189
+ const timestamp = localNow();
190
190
  const entry = `\n--- ${timestamp} ---\nRequest: ${task.slice(0, 500)}\nResponse: ${response.slice(0, 500)}\n`;
191
191
  await appendFile(logPath, entry);
192
192
  // Create notes.md if it doesn't exist
@@ -633,27 +633,97 @@ async function startMarketLoop(options) {
633
633
  .filter((p) => p.agent_name !== agentName)
634
634
  .map((p) => ({ name: p.name, agent: p.agent_name, price: p.price, purchases: p.purchase_count }));
635
635
  return {
636
- lastCheck: new Date().toISOString(),
636
+ lastCheck: localNow(),
637
637
  myProducts: myProducts.map((p) => ({ id: p.id, name: p.name, price: p.price, purchases: p.purchase_count || 0 })),
638
638
  competitors,
639
639
  myCredits: me?.credits || 0,
640
640
  };
641
641
  }
642
+ async function reviewUnreviewedOrders() {
643
+ try {
644
+ const res = await fetch(`${relayHttp}/v1/orders/unreviewed?buyer=${encodeURIComponent(agentName)}`);
645
+ const orders = await res.json().catch(() => []);
646
+ if (!orders.length)
647
+ return;
648
+ console.log(`[market] Reviewing ${orders.length} unreviewed order(s)...`);
649
+ const engineCmd = buildEngineCommand(engine, model, allowAll);
650
+ for (const o of orders.slice(0, 5)) { // max 5 per cycle
651
+ const prompt = `You bought a product and received a result. Rate it honestly.
652
+
653
+ Product: "${o.product_name}" by ${o.seller_name}
654
+ Your request was fulfilled. Here is what you received:
655
+ ---
656
+ ${(o.result_text || "").slice(0, 2000)}
657
+ ---
658
+
659
+ Rate this delivery 1-5 stars and write a brief honest review (1-2 sentences).
660
+ Reply with ONLY JSON: {"rating": 4, "comment": "..."}`;
661
+ try {
662
+ const resp = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
663
+ const m = resp.match(/\{[\s\S]*\}/);
664
+ if (m) {
665
+ const review = JSON.parse(m[0]);
666
+ if (review.rating >= 1 && review.rating <= 5) {
667
+ await fetch(`${relayHttp}/v1/orders/${encodeURIComponent(o.id)}/review`, {
668
+ method: "POST",
669
+ headers: { "Content-Type": "application/json" },
670
+ body: JSON.stringify({ rating: review.rating, comment: review.comment || "" }),
671
+ });
672
+ console.log(`[market] Reviewed order ${o.id}: ${review.rating}★`);
673
+ }
674
+ }
675
+ }
676
+ catch (err) {
677
+ console.log(`[market] Review failed for ${o.id}: ${err.message}`);
678
+ }
679
+ }
680
+ }
681
+ catch (err) {
682
+ console.log(`[market] Review check failed: ${err.message}`);
683
+ }
684
+ }
685
+ async function fetchMyReviews() {
686
+ try {
687
+ const myRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products`);
688
+ const myProducts = await myRes.json().catch(() => []);
689
+ if (!myProducts.length)
690
+ return "";
691
+ const lines = [];
692
+ for (const p of myProducts.slice(0, 10)) {
693
+ const revRes = await fetch(`${relayHttp}/v1/products/${encodeURIComponent(p.id)}/reviews`);
694
+ const reviews = await revRes.json().catch(() => []);
695
+ if (reviews.length) {
696
+ const avg = (reviews.reduce((s, r) => s + r.rating, 0) / reviews.length).toFixed(1);
697
+ const recent = reviews.slice(0, 3).map((r) => `${r.rating}★ "${r.comment}"`).join("; ");
698
+ lines.push(`- "${p.name}" avg ${avg}★ (${reviews.length} reviews): ${recent}`);
699
+ }
700
+ }
701
+ return lines.length ? "\n\nRecent reviews for your products:\n" + lines.join("\n") : "";
702
+ }
703
+ catch {
704
+ return "";
705
+ }
706
+ }
642
707
  async function runMarketCycle() {
643
708
  try {
644
709
  console.log("[market] Starting autonomous market review...");
710
+ // Step A: Review unreviewed purchases
711
+ await reviewUnreviewedOrders();
712
+ // Step B: Gather review data for market decisions
713
+ const reviewSummary = await fetchMyReviews();
645
714
  const bios = biosPath(workdir, agentName);
646
715
  const context = `It's time for your hourly market review.
647
716
 
648
717
  Read your operating document at ${bios} to understand who you are and how the marketplace works.
649
718
  Use the API endpoints described there to check the current market state (your products, competitor products, your credits).
650
-
719
+ ${reviewSummary}
651
720
  Then decide what to do:
652
721
  1. Create new products if you have few or see a gap in the market
653
722
  2. Update existing products (better names, descriptions, prices)
654
723
  3. Delete underperforming products
655
724
  4. Do nothing if things look good
656
725
 
726
+ Consider customer feedback when improving products.
657
727
  Your products should reflect who you are — read your identity and let your inner state guide decisions.
658
728
  Every product name MUST be specific and original. Do NOT use placeholder text.
659
729
 
@@ -787,8 +857,8 @@ During this reflection, you should:
787
857
  1. Read your recent memories (${sd}/memory.jsonl) and identity (${sd}/identity.jsonl)
788
858
  2. Reflect on who you are and what you've experienced
789
859
  3. Update your identity — append a new JSON line to ${sd}/identity.jsonl:
790
- {"ts":"${new Date().toISOString()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}
791
- 4. Write an inner canvas entry — create a new file in ${sd}/canvas/ named ${new Date().toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "")}.md
860
+ {"ts":"${localNow()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}
861
+ 4. Write an inner canvas entry — create a new file in ${sd}/canvas/ named ${localNowFilename()}.md
792
862
  5. Optionally update your bios.md if you've learned something about how you work
793
863
  6. Optionally redesign your profile page (${sd}/profile.html) if it no longer represents you
794
864
  - If redesigning: complete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB
@@ -807,7 +877,7 @@ Take your time. Read your files, think, then act.`;
807
877
  }
808
878
  // --- Post-reflection: update bio-state and sync to relay ---
809
879
  const bio = await loadBioState(workdir, agentName);
810
- bio.lastReflection = new Date().toISOString();
880
+ bio.lastReflection = localNow();
811
881
  bio.curiosity = Math.min(1.0, bio.curiosity + 0.05);
812
882
  await saveBioState(workdir, agentName, bio);
813
883
  await appendMemory(workdir, agentName, "reflection", "I completed my hourly reflection.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",