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/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].filter(Boolean);
90
- return !!token && validTokens.includes(token);
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
- export async function handleSoftwareAgentRunHttp(req, res, deps) {
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.writeHead(400, { "Content-Type": "application/json" })
138
- .end(JSON.stringify({ error: err.message || "Invalid request body" }));
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.writeHead(400, { "Content-Type": "application/json" })
153
- .end(JSON.stringify({ error: err.message || "Invalid software-agent envelope" }));
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 deps.softwareAgent.sendTask(envelope);
158
- res.writeHead(result.success ? 200 : 500, { "Content-Type": "application/json" })
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.writeHead(busy ? 409 : 500, { "Content-Type": "application/json" })
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 (!isOwnerRequest(req, deps.options)) {
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 (!deps.softwareAgent) {
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
- let body;
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 deps.softwareAgent.sendTask(envelope, {
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.writeHead(busy ? 409 : 500, { "Content-Type": "application/json" })
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 (!isOwnerRequest(req, deps.options)) {
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 (!deps.softwareAgent) {
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 (!isOwnerRequest(req, deps.options)) {
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.writeHead(200, { "Content-Type": "application/json" })
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.writeHead(400, { "Content-Type": "application/json" })
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.writeHead(404, { "Content-Type": "application/json" })
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.writeHead(200, { "Content-Type": "application/json" })
314
- .end(JSON.stringify({ task }, null, 2));
300
+ writeJsonResponse(res, 200, { task }, true);
315
301
  return;
316
302
  }
317
- res.writeHead(404, { "Content-Type": "application/json" })
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 (!isOwnerRequest(req, deps.options)) {
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 (!deps.softwareAgent) {
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 deps.softwareAgent.resetSession();
349
- res.writeHead(200, { "Content-Type": "application/json" })
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.writeHead(500, { "Content-Type": "application/json" })
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("owner")
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) {