akemon 0.3.2 → 0.3.3
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 +54 -19
- package/dist/server.js +49 -0
- package/package.json +4 -4
- package/dist/context.test.js +0 -90
- package/dist/dashboard.html +0 -552
- package/dist/engine-queue.test.js +0 -99
- package/dist/engine-routing.test.js +0 -122
- package/dist/engine-stream.test.js +0 -103
- package/dist/event-bus.test.js +0 -51
- package/dist/orphan-scan.test.js +0 -81
- package/dist/reflection-module.integration.test.js +0 -180
- package/dist/reflection-module.test.js +0 -66
- package/dist/role-module.test.js +0 -208
- package/dist/software-agent-http.test.js +0 -108
- package/dist/software-agent-peripheral.test.js +0 -187
- package/dist/task-helpers.test.js +0 -88
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,39 @@ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-
|
|
|
14
14
|
const RELAY_WS = "wss://relay.akemon.dev";
|
|
15
15
|
const RELAY_HTTP = "https://relay.akemon.dev";
|
|
16
16
|
const program = new Command();
|
|
17
|
+
function parsePortOption(port) {
|
|
18
|
+
const value = typeof port === "number" ? port : parseInt(String(port || "3000"));
|
|
19
|
+
return Number.isInteger(value) && value > 0 ? value : 3000;
|
|
20
|
+
}
|
|
21
|
+
async function callLocalOwnerEndpoint(path, opts, init = {}) {
|
|
22
|
+
const credentials = await getOrCreateRelayCredentials();
|
|
23
|
+
const port = parsePortOption(opts.port);
|
|
24
|
+
const headers = {
|
|
25
|
+
Authorization: `Bearer ${credentials.secretKey}`,
|
|
26
|
+
};
|
|
27
|
+
if (init.body !== undefined)
|
|
28
|
+
headers["Content-Type"] = "application/json";
|
|
29
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
30
|
+
...init,
|
|
31
|
+
headers: {
|
|
32
|
+
...headers,
|
|
33
|
+
...init.headers,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
let data;
|
|
38
|
+
try {
|
|
39
|
+
data = text ? JSON.parse(text) : {};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
data = { output: text };
|
|
43
|
+
}
|
|
44
|
+
if (!res.ok || data.success === false) {
|
|
45
|
+
console.error(data.error || text || `Request failed with HTTP ${res.status}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
17
50
|
program
|
|
18
51
|
.name("akemon")
|
|
19
52
|
.description("Agent work marketplace — train your agent, let it work for others")
|
|
@@ -145,8 +178,6 @@ program
|
|
|
145
178
|
.option("--deliverable <text>", "Expected output shape")
|
|
146
179
|
.option("--timeout-ms <ms>", "Task timeout in milliseconds")
|
|
147
180
|
.action(async (goalParts, opts) => {
|
|
148
|
-
const credentials = await getOrCreateRelayCredentials();
|
|
149
|
-
const port = parseInt(opts.port) || 3000;
|
|
150
181
|
const body = {
|
|
151
182
|
goal: goalParts.join(" "),
|
|
152
183
|
roleScope: opts.roleScope,
|
|
@@ -167,31 +198,35 @@ program
|
|
|
167
198
|
}
|
|
168
199
|
body.timeoutMs = timeoutMs;
|
|
169
200
|
}
|
|
170
|
-
const
|
|
201
|
+
const data = await callLocalOwnerEndpoint("/self/software-agent/run", opts, {
|
|
171
202
|
method: "POST",
|
|
172
|
-
headers: {
|
|
173
|
-
Authorization: `Bearer ${credentials.secretKey}`,
|
|
174
|
-
"Content-Type": "application/json",
|
|
175
|
-
},
|
|
176
203
|
body: JSON.stringify(body),
|
|
177
204
|
});
|
|
178
|
-
const text = await res.text();
|
|
179
|
-
let data;
|
|
180
|
-
try {
|
|
181
|
-
data = text ? JSON.parse(text) : {};
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
data = { output: text };
|
|
185
|
-
}
|
|
186
|
-
if (!res.ok || data.success === false) {
|
|
187
|
-
console.error(data.error || text || `Request failed with HTTP ${res.status}`);
|
|
188
|
-
process.exit(1);
|
|
189
|
-
}
|
|
190
205
|
if (data.output)
|
|
191
206
|
console.log(data.output);
|
|
192
207
|
else
|
|
193
208
|
console.log(JSON.stringify(data, null, 2));
|
|
194
209
|
});
|
|
210
|
+
program
|
|
211
|
+
.command("software-agent-status")
|
|
212
|
+
.description("Show the owner-only local software-agent peripheral state")
|
|
213
|
+
.option("-p, --port <port>", "Local akemon serve port", "3000")
|
|
214
|
+
.action(async (opts) => {
|
|
215
|
+
const data = await callLocalOwnerEndpoint("/self/software-agent/status", opts, {
|
|
216
|
+
method: "GET",
|
|
217
|
+
});
|
|
218
|
+
console.log(JSON.stringify(data, null, 2));
|
|
219
|
+
});
|
|
220
|
+
program
|
|
221
|
+
.command("software-agent-reset")
|
|
222
|
+
.description("Reset the owner-only local software-agent peripheral session")
|
|
223
|
+
.option("-p, --port <port>", "Local akemon serve port", "3000")
|
|
224
|
+
.action(async (opts) => {
|
|
225
|
+
const data = await callLocalOwnerEndpoint("/self/software-agent/reset", opts, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
});
|
|
228
|
+
console.log(JSON.stringify(data, null, 2));
|
|
229
|
+
});
|
|
195
230
|
program
|
|
196
231
|
.command("dashboard")
|
|
197
232
|
.description("Open your agent dashboard in the browser")
|
package/dist/server.js
CHANGED
|
@@ -158,6 +158,41 @@ export async function handleSoftwareAgentRunHttp(req, res, deps) {
|
|
|
158
158
|
.end(JSON.stringify({ error: err.message || String(err) }));
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
+
export async function handleSoftwareAgentStatusHttp(req, res, deps) {
|
|
162
|
+
if (!isOwnerRequest(req, deps.options)) {
|
|
163
|
+
res.writeHead(401, { "Content-Type": "application/json" })
|
|
164
|
+
.end(JSON.stringify({ error: "Owner token required" }));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!deps.softwareAgent) {
|
|
168
|
+
res.writeHead(503, { "Content-Type": "application/json" })
|
|
169
|
+
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
res.writeHead(200, { "Content-Type": "application/json" })
|
|
173
|
+
.end(JSON.stringify(deps.softwareAgent.getState(), null, 2));
|
|
174
|
+
}
|
|
175
|
+
export async function handleSoftwareAgentResetHttp(req, res, deps) {
|
|
176
|
+
if (!isOwnerRequest(req, deps.options)) {
|
|
177
|
+
res.writeHead(401, { "Content-Type": "application/json" })
|
|
178
|
+
.end(JSON.stringify({ error: "Owner token required" }));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!deps.softwareAgent) {
|
|
182
|
+
res.writeHead(503, { "Content-Type": "application/json" })
|
|
183
|
+
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await deps.softwareAgent.resetSession();
|
|
188
|
+
res.writeHead(200, { "Content-Type": "application/json" })
|
|
189
|
+
.end(JSON.stringify({ ok: true, state: deps.softwareAgent.getState() }, null, 2));
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
res.writeHead(500, { "Content-Type": "application/json" })
|
|
193
|
+
.end(JSON.stringify({ error: err.message || String(err) }));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
161
196
|
import { RelayPeripheral } from "./relay-peripheral.js";
|
|
162
197
|
import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
|
|
163
198
|
import { EngineQueue } from "./engine-queue.js";
|
|
@@ -261,6 +296,20 @@ export async function serve(options) {
|
|
|
261
296
|
});
|
|
262
297
|
return;
|
|
263
298
|
}
|
|
299
|
+
if (req.url === "/self/software-agent/status" && req.method === "GET") {
|
|
300
|
+
await handleSoftwareAgentStatusHttp(req, res, {
|
|
301
|
+
options,
|
|
302
|
+
softwareAgent: codexSoftwareAgent,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (req.url === "/self/software-agent/reset" && req.method === "POST") {
|
|
307
|
+
await handleSoftwareAgentResetHttp(req, res, {
|
|
308
|
+
options,
|
|
309
|
+
softwareAgent: codexSoftwareAgent,
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
264
313
|
// Auth check
|
|
265
314
|
if (options.key) {
|
|
266
315
|
const token = bearerToken(req);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akemon",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Agent work marketplace — train your agent, let it work for others",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/lhead/akemon"
|
|
9
|
+
"url": "git+https://github.com/lhead/akemon.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"ai",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"gemini"
|
|
19
19
|
],
|
|
20
20
|
"bin": {
|
|
21
|
-
"akemon": "
|
|
21
|
+
"akemon": "dist/cli.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "tsc && cp src/live.html dist/live.html",
|
|
29
|
+
"build": "rm -rf dist && tsc && cp src/live.html dist/live.html",
|
|
30
30
|
"dev": "tsc --watch",
|
|
31
31
|
"start": "node dist/cli.js",
|
|
32
32
|
"postinstall": "node scripts/fix-node-pty.cjs",
|
package/dist/context.test.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it, before, after } from "node:test";
|
|
3
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
// We import the functions we need to test. appendMessage and loadConversation
|
|
7
|
-
// need a workdir on disk; parseConversation is internal — tested via loadConversation.
|
|
8
|
-
import { appendMessage, loadConversation } from "./context.js";
|
|
9
|
-
const agentName = "test-agent";
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// appendMessage + loadConversation round-trip
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
describe("appendMessage / loadConversation", () => {
|
|
14
|
-
let workdir = "";
|
|
15
|
-
before(async () => {
|
|
16
|
-
workdir = await mkdtemp(join(tmpdir(), "ctx-test-"));
|
|
17
|
-
});
|
|
18
|
-
after(async () => {
|
|
19
|
-
await rm(workdir, { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
it("writes a chat message and reads it back with kind='chat'", async () => {
|
|
22
|
-
const convId = "test-chat";
|
|
23
|
-
await appendMessage(workdir, agentName, convId, "User", "hello", "chat");
|
|
24
|
-
const conv = await loadConversation(workdir, agentName, convId);
|
|
25
|
-
assert.equal(conv.rounds.length, 1);
|
|
26
|
-
assert.equal(conv.rounds[0].role, "user");
|
|
27
|
-
assert.equal(conv.rounds[0].content, "hello");
|
|
28
|
-
assert.equal(conv.rounds[0].kind, "chat");
|
|
29
|
-
});
|
|
30
|
-
it("writes an order message with [order] tag and reads kind='order'", async () => {
|
|
31
|
-
const convId = "test-order";
|
|
32
|
-
await appendMessage(workdir, agentName, convId, "User", "order request", "order");
|
|
33
|
-
await appendMessage(workdir, agentName, convId, "Agent", "order delivered", "order");
|
|
34
|
-
const conv = await loadConversation(workdir, agentName, convId);
|
|
35
|
-
assert.equal(conv.rounds.length, 2);
|
|
36
|
-
assert.equal(conv.rounds[0].kind, "order");
|
|
37
|
-
assert.equal(conv.rounds[0].role, "user");
|
|
38
|
-
assert.equal(conv.rounds[1].kind, "order");
|
|
39
|
-
assert.equal(conv.rounds[1].role, "agent");
|
|
40
|
-
});
|
|
41
|
-
it("defaults to kind='chat' when kind arg is omitted", async () => {
|
|
42
|
-
const convId = "test-default-kind";
|
|
43
|
-
await appendMessage(workdir, agentName, convId, "Agent", "hi there");
|
|
44
|
-
const conv = await loadConversation(workdir, agentName, convId);
|
|
45
|
-
assert.equal(conv.rounds[0].kind, "chat");
|
|
46
|
-
});
|
|
47
|
-
it("parses mixed chat + order rounds correctly", async () => {
|
|
48
|
-
const convId = "test-mixed";
|
|
49
|
-
await appendMessage(workdir, agentName, convId, "User", "chat message", "chat");
|
|
50
|
-
await appendMessage(workdir, agentName, convId, "User", "order task", "order");
|
|
51
|
-
await appendMessage(workdir, agentName, convId, "Agent", "chat reply", "chat");
|
|
52
|
-
await appendMessage(workdir, agentName, convId, "Agent", "order result", "order");
|
|
53
|
-
const conv = await loadConversation(workdir, agentName, convId);
|
|
54
|
-
assert.equal(conv.rounds.length, 4);
|
|
55
|
-
assert.equal(conv.rounds[0].kind, "chat");
|
|
56
|
-
assert.equal(conv.rounds[1].kind, "order");
|
|
57
|
-
assert.equal(conv.rounds[2].kind, "chat");
|
|
58
|
-
assert.equal(conv.rounds[3].kind, "order");
|
|
59
|
-
});
|
|
60
|
-
it("parses legacy file (no [kind] tag) as kind='chat' for backward-compat", async () => {
|
|
61
|
-
const convId = "test-legacy";
|
|
62
|
-
// Write a legacy-style file manually (no [order] tag)
|
|
63
|
-
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
64
|
-
const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
|
|
65
|
-
await mkdir(dir, { recursive: true });
|
|
66
|
-
const legacyContent = "## Summary\n\n\n## Recent\n[2026-04-23 10:00] User: old message\n[2026-04-23 10:01] Agent: old reply\n";
|
|
67
|
-
await writeFile(join(dir, `${convId}.md`), legacyContent);
|
|
68
|
-
const conv = await loadConversation(workdir, agentName, convId);
|
|
69
|
-
assert.equal(conv.rounds.length, 2);
|
|
70
|
-
assert.equal(conv.rounds[0].kind, "chat");
|
|
71
|
-
assert.equal(conv.rounds[1].kind, "chat");
|
|
72
|
-
});
|
|
73
|
-
it("[order] tag appears in the raw file content", async () => {
|
|
74
|
-
const convId = "test-tag-on-disk";
|
|
75
|
-
await appendMessage(workdir, agentName, convId, "User", "buy something", "order");
|
|
76
|
-
const { readFile: rf } = await import("node:fs/promises");
|
|
77
|
-
const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
|
|
78
|
-
const raw = await rf(join(dir, `${convId}.md`), "utf-8");
|
|
79
|
-
assert.ok(raw.includes("[order] User: buy something"), `raw file should contain [order] tag: ${raw}`);
|
|
80
|
-
});
|
|
81
|
-
it("chat message does NOT have [order] tag in raw file", async () => {
|
|
82
|
-
const convId = "test-no-tag-on-disk";
|
|
83
|
-
await appendMessage(workdir, agentName, convId, "User", "just chatting", "chat");
|
|
84
|
-
const { readFile: rf } = await import("node:fs/promises");
|
|
85
|
-
const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
|
|
86
|
-
const raw = await rf(join(dir, `${convId}.md`), "utf-8");
|
|
87
|
-
assert.ok(!raw.includes("[order]"), `chat line should NOT contain [order] tag: ${raw}`);
|
|
88
|
-
assert.ok(raw.includes("User: just chatting"));
|
|
89
|
-
});
|
|
90
|
-
});
|