akemon 0.3.4 → 0.3.5
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/README.md +18 -0
- package/dist/cli.js +89 -40
- package/dist/engine-peripheral.js +5 -4
- package/dist/engine-routing.js +99 -0
- package/dist/event-bus.js +63 -17
- package/dist/privacy-filter.js +269 -0
- package/dist/redaction.js +159 -0
- package/dist/relay-client.js +39 -2
- package/dist/server.js +86 -101
- package/dist/software-agent-memory.js +9 -11
- package/dist/software-agent-peripheral.js +313 -20
- package/dist/software-agent-stream-cli.js +101 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
3
|
import { exec } from "child_process";
|
|
4
4
|
import { scanAndKillOrphans } from "./orphan-scan.js";
|
|
5
|
+
import { timingSafeEqual } from "node:crypto";
|
|
5
6
|
import { createServer } from "http";
|
|
6
7
|
import { createInterface } from "readline";
|
|
7
8
|
import { mkdir } from "fs/promises";
|
|
@@ -86,8 +87,35 @@ function bearerToken(req) {
|
|
|
86
87
|
}
|
|
87
88
|
function isOwnerRequest(req, options) {
|
|
88
89
|
const token = bearerToken(req);
|
|
89
|
-
const validTokens = [options.secretKey, options.key]
|
|
90
|
-
|
|
90
|
+
const validTokens = [options.secretKey, options.key]
|
|
91
|
+
.filter((validToken) => typeof validToken === "string" && validToken.length > 0);
|
|
92
|
+
return !!token && validTokens.some((validToken) => constantTimeTokenEqual(token, validToken));
|
|
93
|
+
}
|
|
94
|
+
function constantTimeTokenEqual(left, right) {
|
|
95
|
+
const leftBuffer = Buffer.from(left);
|
|
96
|
+
const rightBuffer = Buffer.from(right);
|
|
97
|
+
const length = Math.max(leftBuffer.length, rightBuffer.length, 1);
|
|
98
|
+
const leftPadded = Buffer.alloc(length);
|
|
99
|
+
const rightPadded = Buffer.alloc(length);
|
|
100
|
+
leftBuffer.copy(leftPadded);
|
|
101
|
+
rightBuffer.copy(rightPadded);
|
|
102
|
+
return timingSafeEqual(leftPadded, rightPadded) && leftBuffer.length === rightBuffer.length;
|
|
103
|
+
}
|
|
104
|
+
function writeJsonResponse(res, statusCode, body, pretty = false) {
|
|
105
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" })
|
|
106
|
+
.end(JSON.stringify(body, null, pretty ? 2 : 0));
|
|
107
|
+
}
|
|
108
|
+
function requireOwnerRequest(req, res, options) {
|
|
109
|
+
if (isOwnerRequest(req, options))
|
|
110
|
+
return true;
|
|
111
|
+
writeJsonResponse(res, 401, { error: "Owner token required" });
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function requireSoftwareAgent(res, softwareAgent) {
|
|
115
|
+
if (softwareAgent)
|
|
116
|
+
return softwareAgent;
|
|
117
|
+
writeJsonResponse(res, 503, { error: "Software agent peripheral not ready" });
|
|
118
|
+
return null;
|
|
91
119
|
}
|
|
92
120
|
function readJsonBody(req, maxBytes = 256 * 1024) {
|
|
93
121
|
return new Promise((resolve, reject) => {
|
|
@@ -118,87 +146,57 @@ function readJsonBody(req, maxBytes = 256 * 1024) {
|
|
|
118
146
|
req.on("error", reject);
|
|
119
147
|
});
|
|
120
148
|
}
|
|
121
|
-
|
|
122
|
-
if (!isOwnerRequest(req, deps.options)) {
|
|
123
|
-
res.writeHead(401, { "Content-Type": "application/json" })
|
|
124
|
-
.end(JSON.stringify({ error: "Owner token required" }));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (!deps.softwareAgent) {
|
|
128
|
-
res.writeHead(503, { "Content-Type": "application/json" })
|
|
129
|
-
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
149
|
+
async function readOwnerSoftwareAgentEnvelope(req, res, deps) {
|
|
132
150
|
let body;
|
|
133
151
|
try {
|
|
134
152
|
body = await readJsonBody(req);
|
|
135
153
|
}
|
|
136
154
|
catch (err) {
|
|
137
|
-
res
|
|
138
|
-
|
|
139
|
-
return;
|
|
155
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid request body" });
|
|
156
|
+
return null;
|
|
140
157
|
}
|
|
141
|
-
let envelope;
|
|
142
158
|
try {
|
|
143
|
-
envelope = createOwnerTaskEnvelope(body, deps.workdir);
|
|
159
|
+
const envelope = createOwnerTaskEnvelope(body, deps.workdir);
|
|
144
160
|
envelope.memorySummary = await buildSoftwareAgentMemorySummary({
|
|
145
161
|
workdir: deps.workdir,
|
|
146
162
|
agentName: deps.agentName,
|
|
147
163
|
envelope,
|
|
148
164
|
request: body,
|
|
149
165
|
});
|
|
166
|
+
return envelope;
|
|
150
167
|
}
|
|
151
168
|
catch (err) {
|
|
152
|
-
res
|
|
153
|
-
|
|
154
|
-
return;
|
|
169
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid software-agent envelope" });
|
|
170
|
+
return null;
|
|
155
171
|
}
|
|
172
|
+
}
|
|
173
|
+
export async function handleSoftwareAgentRunHttp(req, res, deps) {
|
|
174
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
175
|
+
return;
|
|
176
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
177
|
+
if (!softwareAgent)
|
|
178
|
+
return;
|
|
179
|
+
const envelope = await readOwnerSoftwareAgentEnvelope(req, res, deps);
|
|
180
|
+
if (!envelope)
|
|
181
|
+
return;
|
|
156
182
|
try {
|
|
157
|
-
const result = await
|
|
158
|
-
res
|
|
159
|
-
.end(JSON.stringify(result, null, 2));
|
|
183
|
+
const result = await softwareAgent.sendTask(envelope);
|
|
184
|
+
writeJsonResponse(res, 200, redactSecrets(result), true);
|
|
160
185
|
}
|
|
161
186
|
catch (err) {
|
|
162
187
|
const busy = String(err.message || "").includes("busy");
|
|
163
|
-
res
|
|
164
|
-
.end(JSON.stringify({ error: err.message || String(err) }));
|
|
188
|
+
writeJsonResponse(res, busy ? 409 : 500, { error: err.message || String(err) });
|
|
165
189
|
}
|
|
166
190
|
}
|
|
167
191
|
export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
168
|
-
if (!
|
|
169
|
-
res.writeHead(401, { "Content-Type": "application/json" })
|
|
170
|
-
.end(JSON.stringify({ error: "Owner token required" }));
|
|
192
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
171
193
|
return;
|
|
172
|
-
|
|
173
|
-
if (!
|
|
174
|
-
res.writeHead(503, { "Content-Type": "application/json" })
|
|
175
|
-
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
194
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
195
|
+
if (!softwareAgent)
|
|
176
196
|
return;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
body = await readJsonBody(req);
|
|
181
|
-
}
|
|
182
|
-
catch (err) {
|
|
183
|
-
res.writeHead(400, { "Content-Type": "application/json" })
|
|
184
|
-
.end(JSON.stringify({ error: err.message || "Invalid request body" }));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
let envelope;
|
|
188
|
-
try {
|
|
189
|
-
envelope = createOwnerTaskEnvelope(body, deps.workdir);
|
|
190
|
-
envelope.memorySummary = await buildSoftwareAgentMemorySummary({
|
|
191
|
-
workdir: deps.workdir,
|
|
192
|
-
agentName: deps.agentName,
|
|
193
|
-
envelope,
|
|
194
|
-
request: body,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
res.writeHead(400, { "Content-Type": "application/json" })
|
|
199
|
-
.end(JSON.stringify({ error: err.message || "Invalid software-agent envelope" }));
|
|
197
|
+
const envelope = await readOwnerSoftwareAgentEnvelope(req, res, deps);
|
|
198
|
+
if (!envelope)
|
|
200
199
|
return;
|
|
201
|
-
}
|
|
202
200
|
const abortController = new AbortController();
|
|
203
201
|
let responseFinished = false;
|
|
204
202
|
let streamStarted = false;
|
|
@@ -218,7 +216,7 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
218
216
|
res.flushHeaders?.();
|
|
219
217
|
};
|
|
220
218
|
try {
|
|
221
|
-
await
|
|
219
|
+
await softwareAgent.sendTask(envelope, {
|
|
222
220
|
signal: abortController.signal,
|
|
223
221
|
observer: {
|
|
224
222
|
onStart(event) {
|
|
@@ -242,6 +240,8 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
242
240
|
writeSoftwareAgentStreamEvent(res, {
|
|
243
241
|
type: "end",
|
|
244
242
|
taskId: event.taskId,
|
|
243
|
+
exitCode: event.exitCode,
|
|
244
|
+
durationMs: event.durationMs,
|
|
245
245
|
result: event.result,
|
|
246
246
|
});
|
|
247
247
|
},
|
|
@@ -251,8 +251,7 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
251
251
|
catch (err) {
|
|
252
252
|
if (!streamStarted) {
|
|
253
253
|
const busy = String(err.message || "").includes("busy");
|
|
254
|
-
res
|
|
255
|
-
.end(JSON.stringify({ error: err.message || String(err) }));
|
|
254
|
+
writeJsonResponse(res, busy ? 409 : 500, { error: err.message || String(err) });
|
|
256
255
|
responseFinished = true;
|
|
257
256
|
return;
|
|
258
257
|
}
|
|
@@ -268,58 +267,47 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
export async function handleSoftwareAgentStatusHttp(req, res, deps) {
|
|
271
|
-
if (!
|
|
272
|
-
res.writeHead(401, { "Content-Type": "application/json" })
|
|
273
|
-
.end(JSON.stringify({ error: "Owner token required" }));
|
|
270
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
274
271
|
return;
|
|
275
|
-
|
|
276
|
-
if (!
|
|
277
|
-
res.writeHead(503, { "Content-Type": "application/json" })
|
|
278
|
-
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
272
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
273
|
+
if (!softwareAgent)
|
|
279
274
|
return;
|
|
280
|
-
|
|
281
|
-
res.writeHead(200, { "Content-Type": "application/json" })
|
|
282
|
-
.end(JSON.stringify(deps.softwareAgent.getState(), null, 2));
|
|
275
|
+
writeJsonResponse(res, 200, softwareAgent.getState(), true);
|
|
283
276
|
}
|
|
284
277
|
export async function handleSoftwareAgentTasksHttp(req, res, deps) {
|
|
285
|
-
if (!
|
|
286
|
-
res.writeHead(401, { "Content-Type": "application/json" })
|
|
287
|
-
.end(JSON.stringify({ error: "Owner token required" }));
|
|
278
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
288
279
|
return;
|
|
289
|
-
}
|
|
290
280
|
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
291
281
|
const basePath = "/self/software-agent/tasks";
|
|
292
282
|
const taskLedgerDir = softwareAgentTaskLedgerDir(deps.workdir, deps.agentName);
|
|
293
283
|
if (url.pathname === basePath) {
|
|
294
284
|
const limit = readPositiveIntQuery(url.searchParams.get("limit"), 20, 100);
|
|
295
285
|
const tasks = listSoftwareAgentTaskRecords(taskLedgerDir, limit);
|
|
296
|
-
res
|
|
297
|
-
.end(JSON.stringify({ tasks }, null, 2));
|
|
286
|
+
writeJsonResponse(res, 200, { tasks }, true);
|
|
298
287
|
return;
|
|
299
288
|
}
|
|
300
289
|
if (url.pathname.startsWith(`${basePath}/`)) {
|
|
301
290
|
const taskId = decodeURIComponent(url.pathname.slice(basePath.length + 1));
|
|
302
291
|
if (!taskId || taskId.includes("/")) {
|
|
303
|
-
res
|
|
304
|
-
.end(JSON.stringify({ error: "Invalid software-agent task id" }));
|
|
292
|
+
writeJsonResponse(res, 400, { error: "Invalid software-agent task id" });
|
|
305
293
|
return;
|
|
306
294
|
}
|
|
307
295
|
const task = readSoftwareAgentTaskRecord(taskLedgerDir, taskId);
|
|
308
296
|
if (!task) {
|
|
309
|
-
res
|
|
310
|
-
.end(JSON.stringify({ error: "Software-agent task not found" }));
|
|
297
|
+
writeJsonResponse(res, 404, { error: "Software-agent task not found" });
|
|
311
298
|
return;
|
|
312
299
|
}
|
|
313
|
-
res
|
|
314
|
-
.end(JSON.stringify({ task }, null, 2));
|
|
300
|
+
writeJsonResponse(res, 200, { task }, true);
|
|
315
301
|
return;
|
|
316
302
|
}
|
|
317
|
-
res
|
|
318
|
-
.end(JSON.stringify({ error: "Software-agent task endpoint not found" }));
|
|
303
|
+
writeJsonResponse(res, 404, { error: "Software-agent task endpoint not found" });
|
|
319
304
|
}
|
|
320
305
|
function softwareAgentTaskLedgerDir(workdir, agentName) {
|
|
321
306
|
return join(workdir, ".akemon", "agents", agentName, "software-agent", "tasks");
|
|
322
307
|
}
|
|
308
|
+
function softwareAgentContextSessionDir(workdir, agentName) {
|
|
309
|
+
return join(workdir, ".akemon", "agents", agentName, "software-agent", "sessions");
|
|
310
|
+
}
|
|
323
311
|
function readPositiveIntQuery(value, fallback, max) {
|
|
324
312
|
if (!value)
|
|
325
313
|
return fallback;
|
|
@@ -331,27 +319,20 @@ function readPositiveIntQuery(value, fallback, max) {
|
|
|
331
319
|
function writeSoftwareAgentStreamEvent(res, event) {
|
|
332
320
|
if (res.destroyed)
|
|
333
321
|
return;
|
|
334
|
-
res.write(`${JSON.stringify(event)}\n`);
|
|
322
|
+
res.write(`${JSON.stringify(redactSecrets(event))}\n`);
|
|
335
323
|
}
|
|
336
324
|
export async function handleSoftwareAgentResetHttp(req, res, deps) {
|
|
337
|
-
if (!
|
|
338
|
-
res.writeHead(401, { "Content-Type": "application/json" })
|
|
339
|
-
.end(JSON.stringify({ error: "Owner token required" }));
|
|
325
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
340
326
|
return;
|
|
341
|
-
|
|
342
|
-
if (!
|
|
343
|
-
res.writeHead(503, { "Content-Type": "application/json" })
|
|
344
|
-
.end(JSON.stringify({ error: "Software agent peripheral not ready" }));
|
|
327
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
328
|
+
if (!softwareAgent)
|
|
345
329
|
return;
|
|
346
|
-
}
|
|
347
330
|
try {
|
|
348
|
-
await
|
|
349
|
-
res
|
|
350
|
-
.end(JSON.stringify({ ok: true, state: deps.softwareAgent.getState() }, null, 2));
|
|
331
|
+
await softwareAgent.resetSession();
|
|
332
|
+
writeJsonResponse(res, 200, { ok: true, state: softwareAgent.getState() }, true);
|
|
351
333
|
}
|
|
352
334
|
catch (err) {
|
|
353
|
-
res
|
|
354
|
-
.end(JSON.stringify({ error: err.message || String(err) }));
|
|
335
|
+
writeJsonResponse(res, 500, { error: err.message || String(err) });
|
|
355
336
|
}
|
|
356
337
|
}
|
|
357
338
|
import { RelayPeripheral } from "./relay-peripheral.js";
|
|
@@ -370,6 +351,7 @@ import { CodexSoftwareAgentPeripheral, createOwnerTaskEnvelope, listSoftwareAgen
|
|
|
370
351
|
import { buildSoftwareAgentMemorySummary } from "./software-agent-memory.js";
|
|
371
352
|
import { SIG, sig } from "./types.js";
|
|
372
353
|
import { loadConversation, listConversations, buildLLMContext } from "./context.js";
|
|
354
|
+
import { redactSecrets } from "./redaction.js";
|
|
373
355
|
import { createMcpServer, initMcpProxy, createMcpProxyServer } from "./mcp-server.js";
|
|
374
356
|
import { autoRoute, runCollaborativeQuery } from "./agent-utils.js";
|
|
375
357
|
// createMcpServer, initMcpProxy, createMcpProxyServer → see mcp-server.ts
|
|
@@ -378,11 +360,11 @@ const LLM_ENGINES = LLM_ENGINES_SET;
|
|
|
378
360
|
// Engine execution — delegates to EnginePeripheral (V2 Step 3)
|
|
379
361
|
// ---------------------------------------------------------------------------
|
|
380
362
|
/** Unified engine runner — delegates to EnginePeripheral */
|
|
381
|
-
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay, signal, origin, routing, taskId) {
|
|
363
|
+
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay, signal, origin, routing, taskId, routeRequest) {
|
|
382
364
|
if (!_engineP) {
|
|
383
365
|
throw new Error("Engine peripheral not initialized");
|
|
384
366
|
}
|
|
385
|
-
const result = _engineP.runEngine(task, allowAll, extraAllowedTools, signal, origin, routing, taskId);
|
|
367
|
+
const result = _engineP.runEngine(task, allowAll, extraAllowedTools, signal, origin, routing, taskId, routeRequest);
|
|
386
368
|
// Sync trace back to module-level for reporting
|
|
387
369
|
result.then(() => { lastEngineTrace = _engineP.lastTrace; }).catch(() => { lastEngineTrace = _engineP.lastTrace; });
|
|
388
370
|
return result;
|
|
@@ -681,6 +663,9 @@ export async function serve(options) {
|
|
|
681
663
|
model: process.env.AKEMON_CODEX_MODEL,
|
|
682
664
|
sandbox: "workspace-write",
|
|
683
665
|
taskLedgerDir: softwareAgentTaskLedgerDir(workdir, options.agentName),
|
|
666
|
+
contextSessionDir: softwareAgentContextSessionDir(workdir, options.agentName),
|
|
667
|
+
envPolicy: options.softwareAgentEnvPolicy,
|
|
668
|
+
envAllowlist: options.softwareAgentEnvAllowlist,
|
|
684
669
|
});
|
|
685
670
|
// Peripheral registry — Core routes by capability
|
|
686
671
|
const peripherals = [relay, engineP, codexSoftwareAgent];
|
|
@@ -718,7 +703,7 @@ export async function serve(options) {
|
|
|
718
703
|
const abortController = new AbortController();
|
|
719
704
|
const timer = setTimeout(() => abortController.abort(), ENGINE_EXEC_TIMEOUT_MS);
|
|
720
705
|
try {
|
|
721
|
-
const response = await runEngine(options.engine || "claude", options.model, options.allowAll, prompt, workdir, req.tools, req.relay, abortController.signal, req.origin, routing, req.taskId);
|
|
706
|
+
const response = await runEngine(options.engine || "claude", options.model, options.allowAll, prompt, workdir, req.tools, req.relay, abortController.signal, req.origin, routing, req.taskId, req.engineHints);
|
|
722
707
|
emitTokenUsage(prompt.length, response.length);
|
|
723
708
|
return { success: true, response };
|
|
724
709
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { buildLLMContext, loadConversation } from "./context.js";
|
|
2
2
|
import { buildRoleContext, loadRoles, resolveRoles } from "./role-module.js";
|
|
3
3
|
const DEFAULT_CONTEXT_BUDGET = 6000;
|
|
4
|
+
const OWNER_MEMORY_EXCLUDE_TERMS = [
|
|
5
|
+
"owner",
|
|
6
|
+
"private",
|
|
7
|
+
"personal",
|
|
8
|
+
"note",
|
|
9
|
+
"diary",
|
|
10
|
+
"bio",
|
|
11
|
+
];
|
|
4
12
|
export async function buildSoftwareAgentMemorySummary(opts) {
|
|
5
13
|
const budget = opts.contextBudget ?? DEFAULT_CONTEXT_BUDGET;
|
|
6
14
|
const parts = [
|
|
@@ -103,17 +111,7 @@ async function resolveRoleMemoryPolicy(workdir, agentName, roleTrigger) {
|
|
|
103
111
|
function roleExcludesOwnerMemory(policy) {
|
|
104
112
|
return policy.exclude.some((item) => {
|
|
105
113
|
const normalized = item.toLowerCase();
|
|
106
|
-
return normalized.includes(
|
|
107
|
-
|| normalized.includes("private")
|
|
108
|
-
|| normalized.includes("personal")
|
|
109
|
-
|| normalized.includes("note")
|
|
110
|
-
|| normalized.includes("diary")
|
|
111
|
-
|| normalized.includes("bio")
|
|
112
|
-
|| normalized.includes("全部记忆")
|
|
113
|
-
|| normalized.includes("个人")
|
|
114
|
-
|| normalized.includes("笔记")
|
|
115
|
-
|| normalized.includes("日记")
|
|
116
|
-
|| normalized.includes("状态");
|
|
114
|
+
return OWNER_MEMORY_EXCLUDE_TERMS.some((term) => normalized.includes(term));
|
|
117
115
|
});
|
|
118
116
|
}
|
|
119
117
|
function normalizeRequest(value) {
|