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.
- package/dist/server.js +101 -15
- 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:
|
|
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);
|