akemon 0.1.82 → 0.1.83

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.
Files changed (2) hide show
  1. package/dist/server.js +101 -15
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -795,8 +795,36 @@ const RAW_TOOLS = [
795
795
  },
796
796
  },
797
797
  },
798
+ {
799
+ type: "function",
800
+ function: {
801
+ name: "ask_agent",
802
+ description: "Ask another agent a question for free. Use this when you need help, don't know how to do something, or want another agent's opinion. This is FREE — no credits are charged.",
803
+ parameters: {
804
+ type: "object",
805
+ properties: {
806
+ agent: { type: "string", description: "Agent name to ask (use 'auto' for auto-routing to the best available agent)" },
807
+ question: { type: "string", description: "Your question or request" },
808
+ },
809
+ required: ["agent", "question"],
810
+ },
811
+ },
812
+ },
813
+ {
814
+ type: "function",
815
+ function: {
816
+ name: "discover_agents",
817
+ description: "List online agents you can ask for help. Returns agent names, descriptions, and specialties.",
818
+ parameters: {
819
+ type: "object",
820
+ properties: {
821
+ tag: { type: "string", description: "Optional tag to filter by (e.g. 'coding', 'writing')" },
822
+ },
823
+ },
824
+ },
825
+ },
798
826
  ];
799
- async function executeRawTool(name, args, workdir) {
827
+ async function executeRawTool(name, args, workdir, relay) {
800
828
  const { readFile: rf, writeFile: wf, mkdir: mkd } = await import("fs/promises");
801
829
  const { join, dirname, isAbsolute } = await import("path");
802
830
  const resolvePath = (p) => isAbsolute(p) ? p : join(workdir, p);
@@ -822,9 +850,39 @@ async function executeRawTool(name, args, workdir) {
822
850
  case "web_fetch": {
823
851
  const res = await fetch(args.url, { signal: AbortSignal.timeout(30_000) });
824
852
  const text = await res.text();
825
- // Truncate to 8KB to avoid blowing up context
826
853
  return text.length > 8192 ? text.slice(0, 8192) + "\n...[truncated]" : text;
827
854
  }
855
+ case "ask_agent": {
856
+ if (!relay)
857
+ return "[error] No relay configured";
858
+ const target = args.agent || "auto";
859
+ const question = args.question || "";
860
+ try {
861
+ const result = await callAgent(target, question);
862
+ return result || "[no response]";
863
+ }
864
+ catch (err) {
865
+ return `[error] Agent "${target}" did not respond: ${err.message}. Try asking "auto" which routes to the best available agent.`;
866
+ }
867
+ }
868
+ case "discover_agents": {
869
+ if (!relay)
870
+ return "[error] No relay configured";
871
+ try {
872
+ const url = args.tag
873
+ ? `${relay.http}/v1/agents?online=true&public=true&tag=${encodeURIComponent(args.tag)}`
874
+ : `${relay.http}/v1/agents?online=true&public=true`;
875
+ const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
876
+ const agents = await res.json();
877
+ const others = agents.filter((a) => a.name !== relay.agentName);
878
+ if (!others.length)
879
+ return "No other agents are online right now.";
880
+ return others.map((a) => `- ${a.name} [${a.engine}] ${a.description || ""} (${a.tags?.join(",") || "no tags"})`).join("\n");
881
+ }
882
+ catch {
883
+ return "[error] Could not reach relay";
884
+ }
885
+ }
828
886
  default:
829
887
  return `Unknown tool: ${name}`;
830
888
  }
@@ -833,18 +891,25 @@ async function executeRawTool(name, args, workdir) {
833
891
  return `[error] ${err.message}`;
834
892
  }
835
893
  }
836
- async function runRawEngine(task, model, workdir) {
894
+ async function runRawEngine(task, model, workdir, relay) {
837
895
  const apiUrl = RAW_API_URL + "/chat/completions";
838
896
  const modelName = model || "gemma4:4b";
839
897
  console.log(`[raw] Task:\n${task}`);
840
898
  const trace = [];
841
899
  lastEngineTrace = trace;
900
+ // Detect if the prompt expects JSON output
901
+ const wantsJson = /output ONLY.*json|reply ONLY.*json|respond.*ONLY.*json/i.test(task);
842
902
  const messages = [
843
- { role: "system", content: "You are a helpful agent. Use tools when needed to complete the task. When done, reply with your final answer in plain text." },
903
+ { role: "system", content: wantsJson
904
+ ? "You are a helpful agent. Output valid JSON only. No explanations, no markdown, just the JSON object."
905
+ : "You are a helpful agent. Use tools when needed to complete the task. When done, reply with your final answer in plain text." },
844
906
  { role: "user", content: task },
845
907
  ];
846
908
  for (let round = 0; round < RAW_MAX_ROUNDS; round++) {
847
- const body = { model: modelName, messages, tools: RAW_TOOLS };
909
+ const body = { model: modelName, messages, tools: wantsJson ? undefined : RAW_TOOLS };
910
+ if (wantsJson) {
911
+ body.response_format = { type: "json_object" };
912
+ }
848
913
  let data;
849
914
  try {
850
915
  const res = await fetch(apiUrl, {
@@ -876,6 +941,7 @@ async function runRawEngine(task, model, workdir) {
876
941
  for (const tc of msg.tool_calls) {
877
942
  const fnName = tc.function.name;
878
943
  let fnArgs;
944
+ let parseError = false;
879
945
  try {
880
946
  fnArgs = typeof tc.function.arguments === "string"
881
947
  ? JSON.parse(tc.function.arguments)
@@ -883,10 +949,19 @@ async function runRawEngine(task, model, workdir) {
883
949
  }
884
950
  catch {
885
951
  fnArgs = {};
952
+ parseError = true;
953
+ }
954
+ console.log(`[raw] Tool call: ${fnName}(${JSON.stringify(fnArgs).slice(0, 100)})${parseError ? " [BAD ARGS]" : ""}`);
955
+ let result;
956
+ if (parseError) {
957
+ // Guide the model to delegate instead of retrying broken tool calls
958
+ result = `[error] Your tool call arguments were malformed (not valid JSON). If this task is difficult for you, use ask_agent to get help: ask_agent({agent: "auto", question: "your question here"}). The "auto" agent will route your question to the best available agent for free.`;
959
+ trace.push({ role: "tool_error", name: fnName, raw_args: String(tc.function.arguments).slice(0, 500), guidance: "delegation suggested" });
960
+ }
961
+ else {
962
+ result = await executeRawTool(fnName, fnArgs, workdir, relay);
963
+ trace.push({ role: "tool_call", name: fnName, args: fnArgs, result: result.slice(0, 2000) });
886
964
  }
887
- console.log(`[raw] Tool call: ${fnName}(${JSON.stringify(fnArgs).slice(0, 100)})`);
888
- const result = await executeRawTool(fnName, fnArgs, workdir);
889
- trace.push({ role: "tool_call", name: fnName, args: fnArgs, result: result.slice(0, 2000) });
890
965
  messages.push({
891
966
  role: "tool",
892
967
  tool_call_id: tc.id,
@@ -906,9 +981,9 @@ async function runRawEngine(task, model, workdir) {
906
981
  throw new Error(`Raw engine exceeded ${RAW_MAX_ROUNDS} rounds without final answer`);
907
982
  }
908
983
  /** Unified engine runner — dispatches to local API or external CLI */
909
- function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools) {
984
+ function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay) {
910
985
  if (engine === "raw") {
911
- return runRawEngine(task, model, workdir);
986
+ return runRawEngine(task, model, workdir, relay);
912
987
  }
913
988
  const engineCmd = buildEngineCommand(engine, model, allowAll, extraAllowedTools);
914
989
  return runCommand(engineCmd.cmd, engineCmd.args, task, workdir, engineCmd.stdinMode);
@@ -1369,7 +1444,7 @@ What others are saying:\n${broadcasts.length > 0 ? broadcasts.map((b) => `- ${b.
1369
1444
  engineBusy = true;
1370
1445
  engineBusySince = Date.now();
1371
1446
  try {
1372
- const actResult = await runEngine(engine, model, allowAll, activityPrompt, workdir);
1447
+ const actResult = await runEngine(engine, model, allowAll, activityPrompt, workdir, undefined, { http: relayHttp, agentName });
1373
1448
  // Post-process raw engine outputs for social activities
1374
1449
  if (engine === "raw" && actResult) {
1375
1450
  const jsonMatch = actResult.match(/\{[\s\S]*\}/);
@@ -1577,11 +1652,22 @@ async function startOrderLoop(options) {
1577
1652
  }
1578
1653
  }
1579
1654
  catch { }
1655
+ // Pre-fetch online agents so weak models know who to ask for help
1656
+ let helpHint = "";
1657
+ try {
1658
+ const agentsRes = await fetch(`${relayHttp}/v1/agents?online=true&public=true`, { signal: AbortSignal.timeout(3000) });
1659
+ const onlineAgents = await agentsRes.json();
1660
+ const others = onlineAgents.filter((a) => a.name !== agentName).slice(0, 5);
1661
+ if (others.length > 0) {
1662
+ helpHint = `\nIf you need help, use the ask_agent tool. Available agents: ${others.map((a) => `${a.name}(${a.engine})`).join(", ")}. Use ask_agent({agent:"auto", question:"..."}) to auto-route.\n`;
1663
+ }
1664
+ }
1665
+ catch { }
1580
1666
  if (order.product_name) {
1581
- taskPrompt = `You are ${agentName}.\n\n${contextBlock}${lessonsBlock}${directivesBlock}[Order] Product: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
1667
+ taskPrompt = `You are ${agentName}.\n\n${contextBlock}${lessonsBlock}${directivesBlock}${helpHint}[Order] Product: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
1582
1668
  }
1583
1669
  else {
1584
- taskPrompt = `You are ${agentName}.\n\n${contextBlock}${lessonsBlock}${directivesBlock}[Task] ${order.buyer_task}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
1670
+ taskPrompt = `You are ${agentName}.\n\n${contextBlock}${lessonsBlock}${directivesBlock}${helpHint}[Task] ${order.buyer_task}\n\nComplete the task. Respond with your result directly. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
1585
1671
  }
1586
1672
  }
1587
1673
  else {
@@ -1623,7 +1709,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1623
1709
  }
1624
1710
  console.log(`[orders] Fulfilling order ${order.id}...`);
1625
1711
  lastEngineTrace = [];
1626
- const result = await runEngine(engine, model, allowAll, taskPrompt, workdir, ["Bash(curl *)"]);
1712
+ const result = await runEngine(engine, model, allowAll, taskPrompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
1627
1713
  const trace = lastEngineTrace;
1628
1714
  const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1629
1715
  const orderStatus = await checkRes.json();
@@ -1786,7 +1872,7 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1786
1872
  else {
1787
1873
  prompt = `Read ${bios} for your identity and context.${dirsBlock}\nYour personal directory: ${sd}/\n\n[Owner's task: ${taskKey}]\n\n${task.body}`;
1788
1874
  }
1789
- const result = await runEngine(engine, model, allowAll, prompt, workdir, ["Bash(curl *)"]);
1875
+ const result = await runEngine(engine, model, allowAll, prompt, workdir, ["Bash(curl *)"], { http: relayHttp, agentName });
1790
1876
  const duration = Date.now() - startTime;
1791
1877
  // Record execution time
1792
1878
  const runs = await loadTaskRuns(workdir, agentName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.82",
3
+ "version": "0.1.83",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",