akemon 0.3.5 → 0.3.7
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/DATA_POLICY.md +128 -0
- package/README.md +156 -19
- package/TRADEMARK.md +74 -0
- package/dist/akemon-home.js +56 -0
- package/dist/akemon-message.js +107 -0
- package/dist/best-effort.js +8 -0
- package/dist/cli.js +1411 -132
- package/dist/cognitive-artifact-store.js +101 -0
- package/dist/cognitive-event-log.js +47 -0
- package/dist/config.js +45 -9
- package/dist/context.js +27 -6
- package/dist/core/contracts/layers.js +1 -0
- package/dist/core/contracts/permission.js +1 -0
- package/dist/core/contracts/workspace.js +1 -0
- package/dist/core-cognitive-module.js +768 -0
- package/dist/engine-peripheral.js +127 -26
- package/dist/engine-routing.js +58 -17
- package/dist/interactive-session.js +361 -0
- package/dist/local-interconnect.js +156 -0
- package/dist/local-registry.js +178 -0
- package/dist/mcp-server.js +4 -1
- package/dist/memory-proposal.js +379 -0
- package/dist/memory-recorder.js +368 -0
- package/dist/orphan-scan.js +36 -24
- package/dist/passive-reflection-cognitive-module.js +172 -0
- package/dist/peripheral-registry.js +235 -0
- package/dist/permission-audit.js +132 -0
- package/dist/relay-client.js +68 -9
- package/dist/relay-mode.js +34 -0
- package/dist/relay-peripheral.js +139 -49
- package/dist/runtime-platform.js +122 -0
- package/dist/secretariat/client.js +87 -0
- package/dist/self.js +15 -6
- package/dist/server.js +3695 -439
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +314 -235
- package/dist/software-agent-result-cli.js +69 -0
- package/dist/software-agent-stream-cli.js +23 -0
- package/dist/software-agent-transport.js +177 -0
- package/dist/task-module.js +243 -0
- package/dist/task-registry.js +756 -0
- package/dist/vendor/xterm/addon-fit.js +2 -0
- package/dist/vendor/xterm/addon-search.js +2 -0
- package/dist/vendor/xterm/addon-web-links.js +2 -0
- package/dist/vendor/xterm/xterm.css +285 -0
- package/dist/vendor/xterm/xterm.js +2 -0
- package/dist/work-memory.js +339 -0
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +11 -4
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
package/dist/server.js
CHANGED
|
@@ -2,12 +2,48 @@ 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 { registerLocalInstance, touchLocalInstance, unregisterLocalInstance, } from "./local-registry.js";
|
|
6
|
+
import { appendLocalPeerContact, createLocalAkemonResponseMessage, requireLocalAkemonMessageEnvelope, submitLocalAkemonMessageToMcp, } from "./local-interconnect.js";
|
|
7
|
+
import { runLocalCoreCmChat, } from "./core-cognitive-module.js";
|
|
8
|
+
import { runPassiveReflectionOnce, } from "./passive-reflection-cognitive-module.js";
|
|
9
|
+
import { readCognitiveArtifacts, } from "./cognitive-artifact-store.js";
|
|
10
|
+
import { actorRef, textFromAkemonMessage } from "./akemon-message.js";
|
|
11
|
+
import { appendPermissionAuditRecord } from "./permission-audit.js";
|
|
12
|
+
import { logBestEffortError } from "./best-effort.js";
|
|
5
13
|
import { timingSafeEqual } from "node:crypto";
|
|
6
14
|
import { createServer } from "http";
|
|
7
15
|
import { createInterface } from "readline";
|
|
8
|
-
import { mkdir } from "fs/promises";
|
|
9
|
-
import { join } from "path";
|
|
16
|
+
import { mkdir, readFile } from "fs/promises";
|
|
17
|
+
import { dirname, join } from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
10
19
|
import { initWorld, initBioState, initGuide, getSelfState, loadRecentCanvasEntries, initAgentConfig, loadAgentConfig, loadDirectives, loadTaskHistory, reviveAgent, } from "./self.js";
|
|
20
|
+
import { RelayPeripheral } from "./relay-peripheral.js";
|
|
21
|
+
import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
|
|
22
|
+
import { EngineQueue } from "./engine-queue.js";
|
|
23
|
+
import { BioStateModule } from "./bio-module.js";
|
|
24
|
+
import { MemoryModule } from "./memory-module.js";
|
|
25
|
+
import { RoleModule } from "./role-module.js";
|
|
26
|
+
import { TaskModule } from "./task-module.js";
|
|
27
|
+
import { SocialModule } from "./social-module.js";
|
|
28
|
+
import { LongTermModule } from "./longterm-module.js";
|
|
29
|
+
import { ReflectionModule } from "./reflection-module.js";
|
|
30
|
+
import { ScriptModule } from "./script-module.js";
|
|
31
|
+
import { FileEventLog, PersistentEventBus } from "./event-bus.js";
|
|
32
|
+
import { CodexSoftwareAgentPeripheral, createOwnerTaskEnvelope, listSoftwareAgentContextSessions, listSoftwareAgentTaskRecords, readSoftwareAgentContextSession, readSoftwareAgentTaskRecord, softwareAgentTaskRecordPath, } from "./software-agent-peripheral.js";
|
|
33
|
+
import { normalizeWorkbenchInputRequest, normalizeWorkbenchResizeRequest, normalizeWorkbenchStartRequest, normalizeWorkbenchInputMode, normalizeWorkbenchTool, } from "./workbench-session.js";
|
|
34
|
+
import { executeInteractiveSessionAction, InteractiveSessionManager, InteractiveSessionPeripheral, } from "./interactive-session.js";
|
|
35
|
+
import { buildSoftwareAgentMemorySummary } from "./software-agent-memory.js";
|
|
36
|
+
import { buildWorkMemoryContext, workMemoryDir } from "./work-memory.js";
|
|
37
|
+
import { executeMemoryActionProposalWithResult, } from "./memory-proposal.js";
|
|
38
|
+
import { buildPeripheralExploreBriefing, createRuntimePeripheralRecord, loadPeripheralRecords, mergePeripheralRecords, upsertPeripheralRecords, } from "./peripheral-registry.js";
|
|
39
|
+
import { SIG, sig } from "./types.js";
|
|
40
|
+
import { appendRound, loadConversation, listConversations, buildLLMContext } from "./context.js";
|
|
41
|
+
import { redactSecrets } from "./redaction.js";
|
|
42
|
+
import { appendCognitiveEvent, createCognitiveEventId, } from "./cognitive-event-log.js";
|
|
43
|
+
import { createTaskSurfaceView, listTaskRegistryRecords, readTaskRegistryRecord, upsertTaskRegistryRecord, upsertTaskRegistryRecordFromEvent, } from "./task-registry.js";
|
|
44
|
+
import { createMcpServer, initMcpProxy, createMcpProxyServer } from "./mcp-server.js";
|
|
45
|
+
import { autoRoute, runCollaborativeQuery } from "./agent-utils.js";
|
|
46
|
+
const SERVER_DIR = dirname(fileURLToPath(import.meta.url));
|
|
11
47
|
// V2: module-level instances (set in serve())
|
|
12
48
|
let _engineP = null;
|
|
13
49
|
let _bus = null;
|
|
@@ -20,6 +56,7 @@ const ENGINE_WAIT_DEADLINE_MS = 5 * 60 * 1000;
|
|
|
20
56
|
// the slot is returned to the queue. Shorter than the old 8 min — opencode
|
|
21
57
|
// runs that take longer almost always hang forever.
|
|
22
58
|
const ENGINE_EXEC_TIMEOUT_MS = 3 * 60 * 1000;
|
|
59
|
+
const LOCAL_CHAT_FOLLOW_UP_TARGET_MAX_AGE_MS = 30 * 60 * 1000;
|
|
23
60
|
// ---------------------------------------------------------------------------
|
|
24
61
|
// V2 Event helpers — emit signals to EventBus
|
|
25
62
|
// ---------------------------------------------------------------------------
|
|
@@ -37,6 +74,7 @@ function emitTokenUsage(promptLen, resultLen, tokenLimit = 0) {
|
|
|
37
74
|
}));
|
|
38
75
|
}
|
|
39
76
|
}
|
|
77
|
+
const DEFAULT_SEND_INPUT_WAIT_MS = 600;
|
|
40
78
|
// V2: TaskModule ref for push notifications
|
|
41
79
|
let _taskModule = null;
|
|
42
80
|
export function onOrderNotify(orderId) {
|
|
@@ -117,6 +155,68 @@ function requireSoftwareAgent(res, softwareAgent) {
|
|
|
117
155
|
writeJsonResponse(res, 503, { error: "Software agent peripheral not ready" });
|
|
118
156
|
return null;
|
|
119
157
|
}
|
|
158
|
+
const VENDOR_XTERM_NODE_MODULES = {
|
|
159
|
+
"vendor/xterm/xterm.js": join("@xterm", "xterm", "lib", "xterm.js"),
|
|
160
|
+
"vendor/xterm/xterm.css": join("@xterm", "xterm", "css", "xterm.css"),
|
|
161
|
+
"vendor/xterm/addon-fit.js": join("@xterm", "addon-fit", "lib", "addon-fit.js"),
|
|
162
|
+
"vendor/xterm/addon-web-links.js": join("@xterm", "addon-web-links", "lib", "addon-web-links.js"),
|
|
163
|
+
"vendor/xterm/addon-search.js": join("@xterm", "addon-search", "lib", "addon-search.js"),
|
|
164
|
+
};
|
|
165
|
+
async function readUiAsset(relativePath) {
|
|
166
|
+
const candidates = [
|
|
167
|
+
join(SERVER_DIR, relativePath),
|
|
168
|
+
join(SERVER_DIR, "..", "src", relativePath),
|
|
169
|
+
];
|
|
170
|
+
const nodeModulesPath = VENDOR_XTERM_NODE_MODULES[relativePath];
|
|
171
|
+
if (nodeModulesPath) {
|
|
172
|
+
candidates.push(join(SERVER_DIR, "..", "node_modules", nodeModulesPath));
|
|
173
|
+
}
|
|
174
|
+
let lastError;
|
|
175
|
+
for (const candidate of candidates) {
|
|
176
|
+
try {
|
|
177
|
+
return await readFile(candidate);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
lastError = error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw lastError instanceof Error ? lastError : new Error(`UI asset not found: ${relativePath}`);
|
|
184
|
+
}
|
|
185
|
+
export async function handleLocalUiAssetRoute(req, res) {
|
|
186
|
+
const requestPath = req.url?.split("?")[0] || "";
|
|
187
|
+
const routeMap = {
|
|
188
|
+
"/live": { path: "live.html", contentType: "text/html; charset=utf-8", notFound: "Live page not found" },
|
|
189
|
+
"/live/": { path: "live.html", contentType: "text/html; charset=utf-8", notFound: "Live page not found" },
|
|
190
|
+
"/workbench": { path: "workbench.html", contentType: "text/html; charset=utf-8", notFound: "Workbench page not found" },
|
|
191
|
+
"/workbench/": { path: "workbench.html", contentType: "text/html; charset=utf-8", notFound: "Workbench page not found" },
|
|
192
|
+
"/vendor/xterm/xterm.css": { path: "vendor/xterm/xterm.css", contentType: "text/css; charset=utf-8", notFound: "xterm CSS not found" },
|
|
193
|
+
"/vendor/xterm/xterm.js": { path: "vendor/xterm/xterm.js", contentType: "application/javascript; charset=utf-8", notFound: "xterm JS not found" },
|
|
194
|
+
"/vendor/xterm/addon-fit.js": { path: "vendor/xterm/addon-fit.js", contentType: "application/javascript; charset=utf-8", notFound: "xterm addon-fit not found" },
|
|
195
|
+
"/vendor/xterm/addon-web-links.js": { path: "vendor/xterm/addon-web-links.js", contentType: "application/javascript; charset=utf-8", notFound: "xterm addon-web-links not found" },
|
|
196
|
+
"/vendor/xterm/addon-search.js": { path: "vendor/xterm/addon-search.js", contentType: "application/javascript; charset=utf-8", notFound: "xterm addon-search not found" },
|
|
197
|
+
};
|
|
198
|
+
const route = routeMap[requestPath];
|
|
199
|
+
if (!route || req.method !== "GET")
|
|
200
|
+
return false;
|
|
201
|
+
try {
|
|
202
|
+
const asset = await readUiAsset(route.path);
|
|
203
|
+
res.writeHead(200, {
|
|
204
|
+
"Content-Type": route.contentType,
|
|
205
|
+
"Cache-Control": requestPath.startsWith("/vendor/") ? "public, max-age=3600" : "no-store",
|
|
206
|
+
}).end(asset);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" })
|
|
210
|
+
.end(`${route.notFound}: ${err.message || String(err)}`);
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
function requireWorkbench(res, workbench) {
|
|
215
|
+
if (workbench)
|
|
216
|
+
return workbench;
|
|
217
|
+
writeJsonResponse(res, 503, { error: "Workbench session manager not ready" });
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
120
220
|
function readJsonBody(req, maxBytes = 256 * 1024) {
|
|
121
221
|
return new Promise((resolve, reject) => {
|
|
122
222
|
const chunks = [];
|
|
@@ -163,6 +263,16 @@ async function readOwnerSoftwareAgentEnvelope(req, res, deps) {
|
|
|
163
263
|
envelope,
|
|
164
264
|
request: body,
|
|
165
265
|
});
|
|
266
|
+
if (readOptionalBooleanBody(body?.includeWorkMemoryContext, "includeWorkMemoryContext")) {
|
|
267
|
+
const workContext = await buildWorkMemoryContext({
|
|
268
|
+
workdir: deps.workdir,
|
|
269
|
+
agentName: deps.agentName,
|
|
270
|
+
purpose: `software-agent task: ${envelope.goal}`,
|
|
271
|
+
budget: readOptionalPositiveIntBody(body?.workMemoryContextBudget, "workMemoryContextBudget"),
|
|
272
|
+
});
|
|
273
|
+
envelope.workMemoryDir = workContext.workMemoryDir;
|
|
274
|
+
envelope.workMemoryContext = workContext.text;
|
|
275
|
+
}
|
|
166
276
|
return envelope;
|
|
167
277
|
}
|
|
168
278
|
catch (err) {
|
|
@@ -179,134 +289,2821 @@ export async function handleSoftwareAgentRunHttp(req, res, deps) {
|
|
|
179
289
|
const envelope = await readOwnerSoftwareAgentEnvelope(req, res, deps);
|
|
180
290
|
if (!envelope)
|
|
181
291
|
return;
|
|
292
|
+
ensureSoftwareAgentEnvelopeTaskId(envelope);
|
|
293
|
+
const taskEventIds = [];
|
|
294
|
+
const taskStartEventId = await appendSoftwareAgentTaskStartEvent({
|
|
295
|
+
agentName: deps.agentName,
|
|
296
|
+
workdir: deps.workdir,
|
|
297
|
+
envelope,
|
|
298
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
299
|
+
});
|
|
300
|
+
if (taskStartEventId)
|
|
301
|
+
taskEventIds.push(taskStartEventId);
|
|
182
302
|
try {
|
|
183
303
|
const result = await softwareAgent.sendTask(envelope);
|
|
304
|
+
await appendSoftwareAgentTaskEndEvents({
|
|
305
|
+
agentName: deps.agentName,
|
|
306
|
+
workdir: deps.workdir,
|
|
307
|
+
envelope,
|
|
308
|
+
result,
|
|
309
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
310
|
+
sourceEventIds: taskEventIds,
|
|
311
|
+
});
|
|
184
312
|
writeJsonResponse(res, 200, redactSecrets(result), true);
|
|
185
313
|
}
|
|
186
314
|
catch (err) {
|
|
315
|
+
await appendSoftwareAgentTaskFailureEvent({
|
|
316
|
+
agentName: deps.agentName,
|
|
317
|
+
workdir: deps.workdir,
|
|
318
|
+
envelope,
|
|
319
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
320
|
+
sourceEventIds: taskEventIds,
|
|
321
|
+
error: err.message || String(err),
|
|
322
|
+
});
|
|
187
323
|
const busy = String(err.message || "").includes("busy");
|
|
188
324
|
writeJsonResponse(res, busy ? 409 : 500, { error: err.message || String(err) });
|
|
189
325
|
}
|
|
190
326
|
}
|
|
191
|
-
export async function
|
|
327
|
+
export async function handleLocalAkemonMessageHttp(req, res, deps) {
|
|
192
328
|
if (!requireOwnerRequest(req, res, deps.options))
|
|
193
329
|
return;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
330
|
+
let body;
|
|
331
|
+
try {
|
|
332
|
+
body = await readJsonBody(req);
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid request body" });
|
|
199
336
|
return;
|
|
200
|
-
|
|
201
|
-
let
|
|
202
|
-
let
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
337
|
+
}
|
|
338
|
+
let responseSent = false;
|
|
339
|
+
let acceptedAsyncTask = null;
|
|
340
|
+
try {
|
|
341
|
+
const message = requireLocalAkemonMessageEnvelope(body);
|
|
342
|
+
if (message.target.kind !== "agent" || message.target.id !== deps.agentName) {
|
|
343
|
+
writeJsonResponse(res, 400, {
|
|
344
|
+
error: `Message target ${message.target.kind}:${message.target.id} does not match this Akemon (${deps.agentName})`,
|
|
345
|
+
});
|
|
209
346
|
return;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
347
|
+
}
|
|
348
|
+
const text = textFromAkemonMessage(message);
|
|
349
|
+
const followUpForTaskId = readLocalChatFollowUpTaskId(message);
|
|
350
|
+
const memoryCommand = !followUpForTaskId ? parseMemoryChatCommand({
|
|
351
|
+
agentName: deps.agentName,
|
|
352
|
+
workdir: deps.workdir || process.cwd(),
|
|
353
|
+
text,
|
|
354
|
+
}) : null;
|
|
355
|
+
const reflectionCommand = !followUpForTaskId && !memoryCommand
|
|
356
|
+
? executeReflectionChatCommand({
|
|
357
|
+
agentName: deps.agentName,
|
|
358
|
+
workdir: deps.workdir || process.cwd(),
|
|
359
|
+
text,
|
|
360
|
+
})
|
|
361
|
+
: null;
|
|
362
|
+
const workbenchCommand = !followUpForTaskId
|
|
363
|
+
&& deps.workbench
|
|
364
|
+
&& !memoryCommand
|
|
365
|
+
&& !reflectionCommand
|
|
366
|
+
? parseWorkbenchChatCommandAction(text)
|
|
367
|
+
: null;
|
|
368
|
+
const usesCoreCmPath = !followUpForTaskId && !memoryCommand && !reflectionCommand && !workbenchCommand && deps.requestCompute;
|
|
369
|
+
const waitForCompletion = body?.wait !== false && body?.async !== true;
|
|
370
|
+
await appendLocalPeerContact({
|
|
371
|
+
schemaVersion: 1,
|
|
372
|
+
ts: new Date().toISOString(),
|
|
373
|
+
ownerAgent: deps.agentName,
|
|
374
|
+
peerAgent: message.source.id,
|
|
375
|
+
direction: "received",
|
|
376
|
+
messageId: message.id,
|
|
377
|
+
conversationId: message.conversationId,
|
|
215
378
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
379
|
+
try {
|
|
380
|
+
await appendPermissionAuditRecord(deps.agentName, {
|
|
381
|
+
actionKind: "akemon-message",
|
|
382
|
+
action: "local-message.receive",
|
|
383
|
+
requestedBy: message.source,
|
|
384
|
+
performedBy: actorRef("agent", deps.agentName, "local"),
|
|
385
|
+
target: message.target,
|
|
386
|
+
riskLevel: message.permissions.riskLevel,
|
|
387
|
+
memoryScope: message.memoryScope,
|
|
388
|
+
transport: "local",
|
|
389
|
+
decision: {
|
|
390
|
+
result: "allowed",
|
|
391
|
+
mode: message.permissions.requiresOwnerApproval === true ? "owner-approved" : "automatic",
|
|
392
|
+
reason: followUpForTaskId
|
|
393
|
+
? "Owner-only local message queued as a follow-up for an active Akemon task."
|
|
394
|
+
: memoryCommand
|
|
395
|
+
? "Owner-only local message matched a memory command."
|
|
396
|
+
: reflectionCommand
|
|
397
|
+
? "Owner-only local message matched a Reflection CM command."
|
|
398
|
+
: workbenchCommand
|
|
399
|
+
? "Owner-only local message matched a Workbench command."
|
|
400
|
+
: usesCoreCmPath
|
|
401
|
+
? "Owner-only local message entered the Core CM chat path."
|
|
402
|
+
: message.permissions.requiresOwnerApproval === true
|
|
403
|
+
? "Message requested owner approval before local task execution."
|
|
404
|
+
: "Message matched this local Akemon and was forwarded through the local MCP task path.",
|
|
229
405
|
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
chunk: event.chunk,
|
|
236
|
-
});
|
|
406
|
+
allowedActions: message.permissions.allowedActions,
|
|
407
|
+
forbiddenActions: message.permissions.forbiddenActions,
|
|
408
|
+
references: {
|
|
409
|
+
messageId: message.id,
|
|
410
|
+
conversationId: message.conversationId,
|
|
237
411
|
},
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
logBestEffortError("local message audit append", error);
|
|
416
|
+
}
|
|
417
|
+
if (!waitForCompletion && usesCoreCmPath) {
|
|
418
|
+
const workdir = deps.workdir || process.cwd();
|
|
419
|
+
const taskId = createLocalChatTaskId(message);
|
|
420
|
+
const task = await upsertTaskRegistryRecord({
|
|
421
|
+
workdir,
|
|
422
|
+
agentName: deps.agentName,
|
|
423
|
+
patch: {
|
|
424
|
+
taskId,
|
|
425
|
+
source: "local_chat",
|
|
426
|
+
status: "pending",
|
|
427
|
+
stage: "queued",
|
|
428
|
+
objective: text,
|
|
429
|
+
kind: "chat",
|
|
430
|
+
visibility: "primary",
|
|
431
|
+
route: "core_cm",
|
|
432
|
+
conversationId: message.conversationId,
|
|
433
|
+
refs: [{
|
|
434
|
+
conversationId: message.conversationId,
|
|
435
|
+
messageId: message.id,
|
|
436
|
+
taskId,
|
|
437
|
+
}],
|
|
438
|
+
data: {
|
|
439
|
+
source: "local_chat",
|
|
440
|
+
objective: text,
|
|
441
|
+
ownerTextChars: text.length,
|
|
442
|
+
acceptedAsync: true,
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
acceptedAsyncTask = {
|
|
447
|
+
taskId,
|
|
448
|
+
workdir,
|
|
449
|
+
conversationId: message.conversationId,
|
|
450
|
+
messageId: message.id,
|
|
451
|
+
};
|
|
452
|
+
writeJsonResponse(res, 202, {
|
|
453
|
+
success: true,
|
|
454
|
+
accepted: true,
|
|
455
|
+
queued: true,
|
|
456
|
+
async: true,
|
|
457
|
+
wait: false,
|
|
458
|
+
message,
|
|
459
|
+
output: "Akemon accepted this request and will report through the task record.",
|
|
460
|
+
task: createTaskResponseRecord(task),
|
|
461
|
+
taskView: createTaskSurfaceView(task),
|
|
462
|
+
}, true);
|
|
463
|
+
responseSent = true;
|
|
464
|
+
}
|
|
465
|
+
if (followUpForTaskId) {
|
|
466
|
+
await handleLocalChatFollowUpHttp({
|
|
467
|
+
res,
|
|
468
|
+
agentName: deps.agentName,
|
|
469
|
+
workdir: deps.workdir || process.cwd(),
|
|
470
|
+
message,
|
|
471
|
+
taskId: followUpForTaskId,
|
|
472
|
+
text,
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (memoryCommand) {
|
|
477
|
+
const workdir = deps.workdir || process.cwd();
|
|
478
|
+
const taskId = createLocalChatTaskId(message);
|
|
479
|
+
const taskEventIds = [];
|
|
480
|
+
const memoryTaskData = {
|
|
481
|
+
source: "local_chat_command",
|
|
482
|
+
objective: text,
|
|
483
|
+
action: "proposal" in memoryCommand ? memoryCommand.proposal.action : "usage",
|
|
484
|
+
};
|
|
485
|
+
await appendLocalTaskStageEvent({
|
|
486
|
+
taskEventIds,
|
|
487
|
+
agentName: deps.agentName,
|
|
488
|
+
workdir,
|
|
489
|
+
conversationId: message.conversationId,
|
|
490
|
+
taskId,
|
|
491
|
+
phase: "received",
|
|
492
|
+
stage: "queued",
|
|
493
|
+
status: "pending",
|
|
494
|
+
route: "memory_cm",
|
|
495
|
+
summary: "Owner memory command queued.",
|
|
496
|
+
refs: [{
|
|
497
|
+
conversationId: message.conversationId,
|
|
498
|
+
messageId: message.id,
|
|
499
|
+
taskId,
|
|
500
|
+
}],
|
|
501
|
+
data: memoryTaskData,
|
|
502
|
+
});
|
|
503
|
+
await appendLocalTaskStageEvent({
|
|
504
|
+
taskEventIds,
|
|
505
|
+
agentName: deps.agentName,
|
|
506
|
+
workdir,
|
|
507
|
+
conversationId: message.conversationId,
|
|
508
|
+
taskId,
|
|
509
|
+
phase: "routed",
|
|
510
|
+
stage: "framing",
|
|
511
|
+
status: "running",
|
|
512
|
+
route: "memory_cm",
|
|
513
|
+
summary: "Akemon routed the owner request to Memory CM.",
|
|
514
|
+
refs: [{
|
|
515
|
+
conversationId: message.conversationId,
|
|
516
|
+
messageId: message.id,
|
|
517
|
+
taskId,
|
|
518
|
+
}],
|
|
519
|
+
data: memoryTaskData,
|
|
520
|
+
});
|
|
521
|
+
await appendLocalTaskStageEvent({
|
|
522
|
+
taskEventIds,
|
|
523
|
+
agentName: deps.agentName,
|
|
524
|
+
workdir,
|
|
525
|
+
conversationId: message.conversationId,
|
|
526
|
+
taskId,
|
|
527
|
+
phase: "delegated",
|
|
528
|
+
stage: "executing",
|
|
529
|
+
status: "running",
|
|
530
|
+
route: "memory_cm",
|
|
531
|
+
summary: "Memory CM is executing the owner command.",
|
|
532
|
+
refs: [{
|
|
533
|
+
conversationId: message.conversationId,
|
|
534
|
+
messageId: message.id,
|
|
535
|
+
taskId,
|
|
536
|
+
}],
|
|
537
|
+
data: memoryTaskData,
|
|
538
|
+
});
|
|
539
|
+
const memoryResult = "proposal" in memoryCommand
|
|
540
|
+
? await executeMemoryActionProposalWithResult({
|
|
541
|
+
agentName: deps.agentName,
|
|
542
|
+
workdir,
|
|
543
|
+
proposal: memoryCommand.proposal,
|
|
544
|
+
})
|
|
545
|
+
: null;
|
|
546
|
+
const output = memoryResult ? memoryResult.output : ("output" in memoryCommand ? memoryCommand.output : "");
|
|
547
|
+
const response = createLocalAkemonResponseMessage({
|
|
548
|
+
request: message,
|
|
549
|
+
responderAgent: deps.agentName,
|
|
550
|
+
result: output,
|
|
551
|
+
});
|
|
552
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
553
|
+
agentName: deps.agentName,
|
|
554
|
+
workdir,
|
|
555
|
+
conversationId: message.conversationId,
|
|
556
|
+
ownerMessageId: message.id,
|
|
557
|
+
akemonMessageId: response.id,
|
|
558
|
+
taskId,
|
|
559
|
+
userText: text,
|
|
560
|
+
agentText: output,
|
|
561
|
+
});
|
|
562
|
+
const cmEventIds = [];
|
|
563
|
+
if (memoryResult) {
|
|
564
|
+
const cmEventId = await appendMemoryActionCmEvent({
|
|
565
|
+
agentName: deps.agentName,
|
|
566
|
+
workdir,
|
|
567
|
+
conversationId: message.conversationId,
|
|
568
|
+
ownerMessageId: message.id,
|
|
569
|
+
akemonMessageId: response.id,
|
|
570
|
+
sourceEventIds: [...taskEventIds, ...mainChatEventIds],
|
|
571
|
+
taskId,
|
|
572
|
+
memoryAction: memoryResult,
|
|
573
|
+
});
|
|
574
|
+
if (cmEventId)
|
|
575
|
+
cmEventIds.push(cmEventId);
|
|
576
|
+
}
|
|
577
|
+
await appendLocalTaskStageEvent({
|
|
578
|
+
taskEventIds,
|
|
579
|
+
agentName: deps.agentName,
|
|
580
|
+
workdir,
|
|
581
|
+
conversationId: message.conversationId,
|
|
582
|
+
taskId,
|
|
583
|
+
phase: "reported",
|
|
584
|
+
stage: "reviewing",
|
|
585
|
+
status: "running",
|
|
586
|
+
route: "memory_cm",
|
|
587
|
+
summary: "Akemon is preparing the memory command report.",
|
|
588
|
+
refs: [{
|
|
589
|
+
eventIds: [...taskEventIds, ...mainChatEventIds, ...cmEventIds],
|
|
590
|
+
conversationId: message.conversationId,
|
|
591
|
+
messageId: response.id,
|
|
592
|
+
taskId,
|
|
593
|
+
}],
|
|
594
|
+
data: memoryResult
|
|
595
|
+
? createMemoryActionTaskData(memoryResult, "local_chat_command")
|
|
596
|
+
: { source: "local_chat_command", action: "usage" },
|
|
597
|
+
});
|
|
598
|
+
const memoryTaskBlocked = !!(memoryResult && ["not_found", "invalid"].includes(memoryResult.status));
|
|
599
|
+
const taskEndEventId = await appendLocalTaskEvent({
|
|
600
|
+
agentName: deps.agentName,
|
|
601
|
+
workdir,
|
|
602
|
+
conversationId: message.conversationId,
|
|
603
|
+
taskId,
|
|
604
|
+
phase: memoryTaskBlocked ? "failed" : "completed",
|
|
605
|
+
stage: memoryTaskBlocked ? "blocked" : "done",
|
|
606
|
+
status: memoryTaskBlocked ? "failed" : "succeeded",
|
|
607
|
+
route: "memory_cm",
|
|
608
|
+
summary: memoryResult
|
|
609
|
+
? summarizeMemoryActionExecution(memoryResult)
|
|
610
|
+
: "Akemon returned memory command usage guidance.",
|
|
611
|
+
refs: [{
|
|
612
|
+
eventIds: [...taskEventIds, ...mainChatEventIds, ...cmEventIds],
|
|
613
|
+
conversationId: message.conversationId,
|
|
614
|
+
messageId: response.id,
|
|
615
|
+
memoryProposalId: memoryResult?.resolvedProposalId || memoryResult?.proposalId,
|
|
616
|
+
taskId,
|
|
617
|
+
}],
|
|
618
|
+
data: memoryResult
|
|
619
|
+
? createMemoryActionTaskData(memoryResult, "local_chat_command")
|
|
620
|
+
: { source: "local_chat_command", action: "usage" },
|
|
621
|
+
});
|
|
622
|
+
if (taskEndEventId)
|
|
623
|
+
taskEventIds.push(taskEndEventId);
|
|
624
|
+
const task = await readLocalTaskResponseRecord({ workdir, agentName: deps.agentName, taskId });
|
|
625
|
+
writeJsonResponse(res, 200, {
|
|
626
|
+
success: true,
|
|
627
|
+
message,
|
|
628
|
+
response,
|
|
629
|
+
output,
|
|
630
|
+
task: createOptionalTaskResponseRecord(task),
|
|
631
|
+
taskView: createTaskViewResponse(task),
|
|
632
|
+
eventIds: createLocalChatEventIds({
|
|
633
|
+
mainChat: mainChatEventIds,
|
|
634
|
+
cm: cmEventIds,
|
|
635
|
+
task: taskEventIds,
|
|
636
|
+
}),
|
|
637
|
+
}, true);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (reflectionCommand) {
|
|
641
|
+
const output = await reflectionCommand;
|
|
642
|
+
const response = createLocalAkemonResponseMessage({
|
|
643
|
+
request: message,
|
|
644
|
+
responderAgent: deps.agentName,
|
|
645
|
+
result: output,
|
|
646
|
+
});
|
|
647
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
648
|
+
agentName: deps.agentName,
|
|
649
|
+
workdir: deps.workdir || process.cwd(),
|
|
650
|
+
conversationId: message.conversationId,
|
|
651
|
+
ownerMessageId: message.id,
|
|
652
|
+
akemonMessageId: response.id,
|
|
653
|
+
userText: text,
|
|
654
|
+
agentText: output,
|
|
655
|
+
});
|
|
656
|
+
writeJsonResponse(res, 200, {
|
|
657
|
+
success: true,
|
|
658
|
+
message,
|
|
659
|
+
response,
|
|
660
|
+
output,
|
|
661
|
+
eventIds: createLocalChatEventIds({ mainChat: mainChatEventIds }),
|
|
662
|
+
}, true);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (workbenchCommand) {
|
|
666
|
+
const workdir = deps.workdir || process.cwd();
|
|
667
|
+
const taskId = createLocalChatTaskId(message);
|
|
668
|
+
const taskEventIds = [];
|
|
669
|
+
const workbenchTaskData = {
|
|
670
|
+
source: "local_chat_command",
|
|
671
|
+
objective: text,
|
|
672
|
+
capability: "action" in workbenchCommand ? workbenchCommand.action.capability : "invalid_command",
|
|
673
|
+
};
|
|
674
|
+
await appendLocalTaskStageEvent({
|
|
675
|
+
taskEventIds,
|
|
676
|
+
agentName: deps.agentName,
|
|
677
|
+
workdir,
|
|
678
|
+
conversationId: message.conversationId,
|
|
679
|
+
taskId,
|
|
680
|
+
phase: "received",
|
|
681
|
+
stage: "queued",
|
|
682
|
+
status: "pending",
|
|
683
|
+
route: "interactive-session",
|
|
684
|
+
summary: "Owner Workbench command queued.",
|
|
685
|
+
refs: [{
|
|
686
|
+
conversationId: message.conversationId,
|
|
687
|
+
messageId: message.id,
|
|
688
|
+
taskId,
|
|
689
|
+
}],
|
|
690
|
+
data: workbenchTaskData,
|
|
691
|
+
});
|
|
692
|
+
await appendLocalTaskStageEvent({
|
|
693
|
+
taskEventIds,
|
|
694
|
+
agentName: deps.agentName,
|
|
695
|
+
workdir,
|
|
696
|
+
conversationId: message.conversationId,
|
|
697
|
+
taskId,
|
|
698
|
+
phase: "delegated",
|
|
699
|
+
stage: "dispatching",
|
|
700
|
+
status: "running",
|
|
701
|
+
route: "interactive-session",
|
|
702
|
+
summary: "action" in workbenchCommand
|
|
703
|
+
? `Owner asked Akemon to run interactive-session.${workbenchCommand.action.capability}.`
|
|
704
|
+
: "Owner entered an invalid Workbench command.",
|
|
705
|
+
refs: [{
|
|
706
|
+
conversationId: message.conversationId,
|
|
707
|
+
messageId: message.id,
|
|
708
|
+
taskId,
|
|
709
|
+
}],
|
|
710
|
+
data: workbenchTaskData,
|
|
711
|
+
});
|
|
712
|
+
const observation = "action" in workbenchCommand
|
|
713
|
+
? await executeInteractiveSessionViaPeripheral(deps.workbench, deps.interactiveSessionPeripheral, workbenchCommand.action)
|
|
714
|
+
: createInvalidInteractiveSessionObservation(workbenchCommand.error);
|
|
715
|
+
const peripheralEventId = "action" in workbenchCommand
|
|
716
|
+
? await appendInteractiveSessionPeripheralEvent({
|
|
717
|
+
agentName: deps.agentName,
|
|
718
|
+
workdir,
|
|
719
|
+
conversationId: message.conversationId,
|
|
720
|
+
ownerMessageId: message.id,
|
|
721
|
+
taskId,
|
|
722
|
+
action: workbenchCommand.action,
|
|
723
|
+
observation,
|
|
724
|
+
})
|
|
725
|
+
: null;
|
|
726
|
+
const peripheralEventIds = peripheralEventId ? [peripheralEventId] : [];
|
|
727
|
+
const output = observation.summary;
|
|
728
|
+
const response = createLocalAkemonResponseMessage({
|
|
729
|
+
request: message,
|
|
730
|
+
responderAgent: deps.agentName,
|
|
731
|
+
result: output,
|
|
732
|
+
});
|
|
733
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
734
|
+
agentName: deps.agentName,
|
|
735
|
+
workdir: deps.workdir || process.cwd(),
|
|
736
|
+
conversationId: message.conversationId,
|
|
737
|
+
ownerMessageId: message.id,
|
|
738
|
+
akemonMessageId: response.id,
|
|
739
|
+
taskId,
|
|
740
|
+
userText: text,
|
|
741
|
+
agentText: output,
|
|
742
|
+
});
|
|
743
|
+
await appendLocalTaskStageEvent({
|
|
744
|
+
taskEventIds,
|
|
745
|
+
agentName: deps.agentName,
|
|
746
|
+
workdir,
|
|
747
|
+
conversationId: message.conversationId,
|
|
748
|
+
taskId,
|
|
749
|
+
phase: "observed",
|
|
750
|
+
stage: "observing",
|
|
751
|
+
status: "running",
|
|
752
|
+
route: "interactive-session",
|
|
753
|
+
summary: observation.ok
|
|
754
|
+
? "Akemon observed the interactive-session result."
|
|
755
|
+
: "Akemon observed an interactive-session error.",
|
|
756
|
+
refs: [{
|
|
757
|
+
eventIds: [...taskEventIds, ...peripheralEventIds],
|
|
758
|
+
conversationId: message.conversationId,
|
|
759
|
+
messageId: response.id,
|
|
760
|
+
taskId,
|
|
761
|
+
sessionId: interactiveSessionObservationSessionId(observation, "action" in workbenchCommand ? workbenchCommand.action : undefined),
|
|
762
|
+
}],
|
|
763
|
+
data: createInteractiveSessionTaskData({
|
|
764
|
+
source: "local_chat_command",
|
|
765
|
+
action: "action" in workbenchCommand ? workbenchCommand.action : undefined,
|
|
766
|
+
observation,
|
|
767
|
+
outputChars: output.length,
|
|
768
|
+
}),
|
|
769
|
+
});
|
|
770
|
+
await appendLocalTaskStageEvent({
|
|
771
|
+
taskEventIds,
|
|
772
|
+
agentName: deps.agentName,
|
|
773
|
+
workdir,
|
|
774
|
+
conversationId: message.conversationId,
|
|
775
|
+
taskId,
|
|
776
|
+
phase: "reported",
|
|
777
|
+
stage: "reviewing",
|
|
778
|
+
status: "running",
|
|
779
|
+
route: "interactive-session",
|
|
780
|
+
summary: "Akemon is preparing the interactive-session report.",
|
|
781
|
+
refs: [{
|
|
782
|
+
eventIds: [...taskEventIds, ...mainChatEventIds, ...peripheralEventIds],
|
|
783
|
+
conversationId: message.conversationId,
|
|
784
|
+
messageId: response.id,
|
|
785
|
+
taskId,
|
|
786
|
+
sessionId: interactiveSessionObservationSessionId(observation, "action" in workbenchCommand ? workbenchCommand.action : undefined),
|
|
787
|
+
}],
|
|
788
|
+
data: createInteractiveSessionTaskData({
|
|
789
|
+
source: "local_chat_command",
|
|
790
|
+
action: "action" in workbenchCommand ? workbenchCommand.action : undefined,
|
|
791
|
+
observation,
|
|
792
|
+
outputChars: output.length,
|
|
793
|
+
}),
|
|
794
|
+
});
|
|
795
|
+
const taskEndEventId = await appendLocalTaskEvent({
|
|
796
|
+
agentName: deps.agentName,
|
|
797
|
+
workdir,
|
|
798
|
+
conversationId: message.conversationId,
|
|
799
|
+
taskId,
|
|
800
|
+
phase: observation.ok ? "completed" : "failed",
|
|
801
|
+
stage: observation.ok ? "done" : "blocked",
|
|
802
|
+
status: observation.ok ? "succeeded" : "failed",
|
|
803
|
+
route: "interactive-session",
|
|
804
|
+
summary: summarizeInteractiveSessionTaskCompletion(observation.capability, observation.ok),
|
|
805
|
+
refs: [{
|
|
806
|
+
eventIds: [...taskEventIds, ...mainChatEventIds, ...peripheralEventIds],
|
|
807
|
+
conversationId: message.conversationId,
|
|
808
|
+
messageId: response.id,
|
|
809
|
+
taskId,
|
|
810
|
+
sessionId: interactiveSessionObservationSessionId(observation, "action" in workbenchCommand ? workbenchCommand.action : undefined),
|
|
811
|
+
}],
|
|
812
|
+
data: createInteractiveSessionTaskData({
|
|
813
|
+
source: "local_chat_command",
|
|
814
|
+
action: "action" in workbenchCommand ? workbenchCommand.action : undefined,
|
|
815
|
+
observation,
|
|
816
|
+
outputChars: output.length,
|
|
817
|
+
}),
|
|
818
|
+
});
|
|
819
|
+
if (taskEndEventId)
|
|
820
|
+
taskEventIds.push(taskEndEventId);
|
|
821
|
+
const task = await readLocalTaskResponseRecord({ workdir, agentName: deps.agentName, taskId });
|
|
822
|
+
writeJsonResponse(res, 200, {
|
|
823
|
+
success: true,
|
|
824
|
+
message,
|
|
825
|
+
response,
|
|
826
|
+
output,
|
|
827
|
+
task: createOptionalTaskResponseRecord(task),
|
|
828
|
+
taskView: createTaskViewResponse(task),
|
|
829
|
+
eventIds: createLocalChatEventIds({
|
|
830
|
+
mainChat: mainChatEventIds,
|
|
831
|
+
peripheral: peripheralEventIds,
|
|
832
|
+
task: taskEventIds,
|
|
833
|
+
}),
|
|
834
|
+
}, true);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (deps.requestCompute) {
|
|
838
|
+
const ownerLanguage = (await loadAgentConfig(deps.workdir || process.cwd(), deps.agentName)).owner_language;
|
|
839
|
+
const workdir = deps.workdir || process.cwd();
|
|
840
|
+
const taskId = createLocalChatTaskId(message);
|
|
841
|
+
const taskEventIds = [];
|
|
842
|
+
const coreTaskData = {
|
|
843
|
+
source: "local_chat",
|
|
844
|
+
objective: text,
|
|
845
|
+
ownerTextChars: text.length,
|
|
846
|
+
};
|
|
847
|
+
await appendLocalTaskStageEvent({
|
|
848
|
+
taskEventIds,
|
|
849
|
+
agentName: deps.agentName,
|
|
850
|
+
workdir,
|
|
851
|
+
conversationId: message.conversationId,
|
|
852
|
+
taskId,
|
|
853
|
+
phase: "received",
|
|
854
|
+
stage: "queued",
|
|
855
|
+
status: "pending",
|
|
856
|
+
route: "core_cm",
|
|
857
|
+
summary: "Owner request queued for Core CM.",
|
|
858
|
+
refs: [{
|
|
859
|
+
conversationId: message.conversationId,
|
|
860
|
+
messageId: message.id,
|
|
861
|
+
taskId,
|
|
862
|
+
}],
|
|
863
|
+
data: coreTaskData,
|
|
864
|
+
});
|
|
865
|
+
await appendLocalTaskStageEvent({
|
|
866
|
+
taskEventIds,
|
|
867
|
+
agentName: deps.agentName,
|
|
868
|
+
workdir,
|
|
869
|
+
conversationId: message.conversationId,
|
|
870
|
+
taskId,
|
|
871
|
+
phase: "routed",
|
|
872
|
+
stage: "framing",
|
|
873
|
+
status: "running",
|
|
874
|
+
route: "core_cm",
|
|
875
|
+
summary: "Core CM is framing the owner request.",
|
|
876
|
+
refs: [{
|
|
877
|
+
conversationId: message.conversationId,
|
|
878
|
+
messageId: message.id,
|
|
879
|
+
taskId,
|
|
880
|
+
}],
|
|
881
|
+
data: coreTaskData,
|
|
882
|
+
});
|
|
883
|
+
const peripheralEventIds = [];
|
|
884
|
+
const computeEventIds = [];
|
|
885
|
+
const dispatchCmEventIds = [];
|
|
886
|
+
const dispatchRecordsSeen = [];
|
|
887
|
+
let ownerTextForDispatch = text;
|
|
888
|
+
const requestCoreCmCompute = async (req) => {
|
|
889
|
+
const computeResult = await deps.requestCompute({
|
|
890
|
+
...req,
|
|
891
|
+
taskId: req.taskId || taskId,
|
|
892
|
+
refs: [
|
|
893
|
+
...(req.refs || []),
|
|
894
|
+
{
|
|
895
|
+
conversationId: message.conversationId,
|
|
896
|
+
messageId: message.id,
|
|
897
|
+
taskId,
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
});
|
|
901
|
+
if (computeResult.audit?.eventId)
|
|
902
|
+
computeEventIds.push(computeResult.audit.eventId);
|
|
903
|
+
return computeResult;
|
|
904
|
+
};
|
|
905
|
+
const executeCoreCmInteractiveSessionProposal = async (proposal) => {
|
|
906
|
+
const existingTask = await readTaskRegistryRecord({
|
|
907
|
+
workdir,
|
|
908
|
+
agentName: deps.agentName,
|
|
909
|
+
taskId,
|
|
910
|
+
});
|
|
911
|
+
const dispatchRecord = createCoreCmDispatchRecord({
|
|
912
|
+
proposal,
|
|
913
|
+
ownerText: ownerTextForDispatch,
|
|
914
|
+
taskId,
|
|
915
|
+
conversationId: message.conversationId,
|
|
916
|
+
});
|
|
917
|
+
dispatchRecordsSeen.push(dispatchRecord);
|
|
918
|
+
const dispatchRecords = appendTaskDispatchRecord(existingTask, dispatchRecord);
|
|
919
|
+
const dispatchCmEventId = await appendCoreCmDispatchRecordEvent({
|
|
920
|
+
agentName: deps.agentName,
|
|
921
|
+
workdir,
|
|
922
|
+
conversationId: message.conversationId,
|
|
923
|
+
ownerMessageId: message.id,
|
|
924
|
+
taskId,
|
|
925
|
+
dispatchRecord,
|
|
926
|
+
sourceEventIds: taskEventIds,
|
|
927
|
+
});
|
|
928
|
+
if (dispatchCmEventId)
|
|
929
|
+
dispatchCmEventIds.push(dispatchCmEventId);
|
|
930
|
+
await appendLocalTaskStageEvent({
|
|
931
|
+
taskEventIds,
|
|
932
|
+
agentName: deps.agentName,
|
|
933
|
+
workdir,
|
|
934
|
+
conversationId: message.conversationId,
|
|
935
|
+
taskId,
|
|
936
|
+
phase: "delegated",
|
|
937
|
+
stage: "dispatching",
|
|
938
|
+
status: "running",
|
|
939
|
+
route: "core_cm",
|
|
940
|
+
summary: `Core CM dispatched ${proposal.capability} to interactive-session.`,
|
|
941
|
+
refs: [{
|
|
942
|
+
conversationId: message.conversationId,
|
|
943
|
+
messageId: message.id,
|
|
944
|
+
taskId,
|
|
945
|
+
}],
|
|
946
|
+
data: {
|
|
947
|
+
...coreTaskData,
|
|
948
|
+
peripheral: proposal.peripheral,
|
|
949
|
+
capability: proposal.capability,
|
|
950
|
+
dispatchRecord,
|
|
951
|
+
dispatchRecords,
|
|
952
|
+
dispatchRecordCount: dispatchRecords.length,
|
|
953
|
+
latestDispatchTarget: dispatchRecord.targetPeripheral,
|
|
954
|
+
latestDispatchCapability: dispatchRecord.capability,
|
|
955
|
+
},
|
|
956
|
+
});
|
|
957
|
+
const observation = await executeInteractiveSessionActionProposal(deps.workbench, deps.interactiveSessionPeripheral, proposal);
|
|
958
|
+
const eventId = await appendInteractiveSessionPeripheralEvent({
|
|
959
|
+
agentName: deps.agentName,
|
|
960
|
+
workdir: deps.workdir || process.cwd(),
|
|
961
|
+
conversationId: message.conversationId,
|
|
962
|
+
ownerMessageId: message.id,
|
|
963
|
+
taskId,
|
|
964
|
+
action: proposal,
|
|
965
|
+
observation,
|
|
966
|
+
});
|
|
967
|
+
if (eventId)
|
|
968
|
+
peripheralEventIds.push(eventId);
|
|
969
|
+
await appendLocalTaskStageEvent({
|
|
970
|
+
taskEventIds,
|
|
971
|
+
agentName: deps.agentName,
|
|
972
|
+
workdir,
|
|
973
|
+
conversationId: message.conversationId,
|
|
974
|
+
taskId,
|
|
975
|
+
phase: "observed",
|
|
976
|
+
stage: "observing",
|
|
977
|
+
status: "running",
|
|
978
|
+
route: "core_cm",
|
|
979
|
+
summary: observation.ok
|
|
980
|
+
? "Core CM observed the peripheral result."
|
|
981
|
+
: "Core CM observed a peripheral error.",
|
|
982
|
+
refs: [{
|
|
983
|
+
eventIds: eventId ? [eventId] : [],
|
|
984
|
+
conversationId: message.conversationId,
|
|
985
|
+
messageId: message.id,
|
|
986
|
+
taskId,
|
|
987
|
+
sessionId: interactiveSessionObservationSessionId(observation, proposal),
|
|
988
|
+
}],
|
|
989
|
+
data: {
|
|
990
|
+
...coreTaskData,
|
|
991
|
+
peripheral: proposal.peripheral,
|
|
992
|
+
capability: proposal.capability,
|
|
993
|
+
observationOk: observation.ok,
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
return observation;
|
|
997
|
+
};
|
|
998
|
+
let result = await runLocalCoreCmChat({
|
|
999
|
+
agentName: deps.agentName,
|
|
1000
|
+
workdir,
|
|
1001
|
+
text,
|
|
1002
|
+
conversationId: message.conversationId,
|
|
1003
|
+
ownerLanguage,
|
|
1004
|
+
interactiveSessions: deps.workbench,
|
|
1005
|
+
requestCompute: requestCoreCmCompute,
|
|
1006
|
+
executeInteractiveSessionActionProposal: executeCoreCmInteractiveSessionProposal,
|
|
1007
|
+
});
|
|
1008
|
+
let absorbedFollowUps = [];
|
|
1009
|
+
const queuedFollowUps = await readQueuedActionableFollowUps({
|
|
1010
|
+
workdir,
|
|
1011
|
+
agentName: deps.agentName,
|
|
1012
|
+
taskId,
|
|
1013
|
+
});
|
|
1014
|
+
if (queuedFollowUps.length) {
|
|
1015
|
+
const followUpText = formatCoreCmQueuedFollowUpText({
|
|
1016
|
+
originalText: text,
|
|
1017
|
+
currentOutput: result.output,
|
|
1018
|
+
followUps: queuedFollowUps,
|
|
1019
|
+
});
|
|
1020
|
+
const previousOwnerTextForDispatch = ownerTextForDispatch;
|
|
1021
|
+
ownerTextForDispatch = followUpText;
|
|
1022
|
+
try {
|
|
1023
|
+
const followUpResult = await runLocalCoreCmChat({
|
|
1024
|
+
agentName: deps.agentName,
|
|
1025
|
+
workdir,
|
|
1026
|
+
text: followUpText,
|
|
1027
|
+
conversationId: message.conversationId,
|
|
1028
|
+
ownerLanguage,
|
|
1029
|
+
interactiveSessions: deps.workbench,
|
|
1030
|
+
requestCompute: requestCoreCmCompute,
|
|
1031
|
+
executeInteractiveSessionActionProposal: executeCoreCmInteractiveSessionProposal,
|
|
246
1032
|
});
|
|
1033
|
+
result = mergeCoreCmQueuedFollowUpResult({
|
|
1034
|
+
base: result,
|
|
1035
|
+
followUp: followUpResult,
|
|
1036
|
+
followUps: queuedFollowUps,
|
|
1037
|
+
ownerLanguage,
|
|
1038
|
+
});
|
|
1039
|
+
absorbedFollowUps = queuedFollowUps;
|
|
1040
|
+
}
|
|
1041
|
+
finally {
|
|
1042
|
+
ownerTextForDispatch = previousOwnerTextForDispatch;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const output = result.output;
|
|
1046
|
+
const response = createLocalAkemonResponseMessage({
|
|
1047
|
+
request: message,
|
|
1048
|
+
responderAgent: deps.agentName,
|
|
1049
|
+
result: output,
|
|
1050
|
+
});
|
|
1051
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
1052
|
+
agentName: deps.agentName,
|
|
1053
|
+
workdir: deps.workdir || process.cwd(),
|
|
1054
|
+
conversationId: message.conversationId,
|
|
1055
|
+
ownerMessageId: message.id,
|
|
1056
|
+
akemonMessageId: response.id,
|
|
1057
|
+
taskId,
|
|
1058
|
+
userText: text,
|
|
1059
|
+
agentText: output,
|
|
1060
|
+
});
|
|
1061
|
+
const cmEventIds = await appendCoreCmResultEvents({
|
|
1062
|
+
agentName: deps.agentName,
|
|
1063
|
+
workdir: deps.workdir || process.cwd(),
|
|
1064
|
+
conversationId: message.conversationId,
|
|
1065
|
+
ownerMessageId: message.id,
|
|
1066
|
+
akemonMessageId: response.id,
|
|
1067
|
+
sourceEventIds: [...dispatchCmEventIds, ...mainChatEventIds, ...peripheralEventIds],
|
|
1068
|
+
taskId,
|
|
1069
|
+
processNotes: result.processNotes,
|
|
1070
|
+
memoryProposal: result.memoryProposal,
|
|
1071
|
+
memoryAction: result.memoryAction,
|
|
1072
|
+
});
|
|
1073
|
+
const existingReviewTask = await readTaskRegistryRecord({
|
|
1074
|
+
workdir,
|
|
1075
|
+
agentName: deps.agentName,
|
|
1076
|
+
taskId,
|
|
1077
|
+
});
|
|
1078
|
+
const followUpTaskData = createAbsorbedFollowUpTaskData(existingReviewTask, absorbedFollowUps);
|
|
1079
|
+
const reviewRecord = createCoreCmReviewRecord({
|
|
1080
|
+
result,
|
|
1081
|
+
output,
|
|
1082
|
+
taskId,
|
|
1083
|
+
conversationId: message.conversationId,
|
|
1084
|
+
dispatchRecord: dispatchRecordsSeen[dispatchRecordsSeen.length - 1],
|
|
1085
|
+
});
|
|
1086
|
+
const reviewRecords = appendTaskReviewRecord(existingReviewTask, reviewRecord);
|
|
1087
|
+
const reviewCmEventId = await appendCoreCmReviewRecordEvent({
|
|
1088
|
+
agentName: deps.agentName,
|
|
1089
|
+
workdir: deps.workdir || process.cwd(),
|
|
1090
|
+
conversationId: message.conversationId,
|
|
1091
|
+
ownerMessageId: message.id,
|
|
1092
|
+
akemonMessageId: response.id,
|
|
1093
|
+
taskId,
|
|
1094
|
+
reviewRecord,
|
|
1095
|
+
sourceEventIds: [...dispatchCmEventIds, ...mainChatEventIds, ...peripheralEventIds, ...cmEventIds, ...computeEventIds],
|
|
1096
|
+
});
|
|
1097
|
+
const reviewCmEventIds = reviewCmEventId ? [reviewCmEventId] : [];
|
|
1098
|
+
await appendLocalTaskStageEvent({
|
|
1099
|
+
taskEventIds,
|
|
1100
|
+
agentName: deps.agentName,
|
|
1101
|
+
workdir,
|
|
1102
|
+
conversationId: message.conversationId,
|
|
1103
|
+
taskId,
|
|
1104
|
+
phase: "reported",
|
|
1105
|
+
stage: "reviewing",
|
|
1106
|
+
status: "running",
|
|
1107
|
+
route: "core_cm",
|
|
1108
|
+
summary: "Core CM is reviewing the result for the owner report.",
|
|
1109
|
+
refs: [{
|
|
1110
|
+
eventIds: [...taskEventIds, ...dispatchCmEventIds, ...mainChatEventIds, ...peripheralEventIds, ...cmEventIds, ...reviewCmEventIds, ...computeEventIds],
|
|
1111
|
+
conversationId: message.conversationId,
|
|
1112
|
+
messageId: response.id,
|
|
1113
|
+
taskId,
|
|
1114
|
+
...(result.memoryProposal?.id ? { memoryProposalId: result.memoryProposal.id } : {}),
|
|
1115
|
+
}],
|
|
1116
|
+
data: {
|
|
1117
|
+
...coreTaskData,
|
|
1118
|
+
...followUpTaskData,
|
|
1119
|
+
outputChars: output.length,
|
|
1120
|
+
peripheralEventCount: peripheralEventIds.length,
|
|
1121
|
+
cmEventCount: dispatchCmEventIds.length + cmEventIds.length + reviewCmEventIds.length,
|
|
1122
|
+
computeEventCount: computeEventIds.length,
|
|
1123
|
+
reviewRecord,
|
|
1124
|
+
reviewRecords,
|
|
1125
|
+
reviewRecordCount: reviewRecords.length,
|
|
1126
|
+
latestReviewQuality: reviewRecord.resultQuality,
|
|
1127
|
+
latestCompletionDecision: reviewRecord.completionDecision,
|
|
1128
|
+
followUpNeeded: reviewRecord.followUpNeeded,
|
|
1129
|
+
memoryProposalRefs: reviewRecord.memoryProposalRefs,
|
|
1130
|
+
reportText: reviewRecord.reportText,
|
|
1131
|
+
...(result.memoryProposal?.id ? { memoryProposalId: result.memoryProposal.id } : {}),
|
|
247
1132
|
},
|
|
248
|
-
}
|
|
1133
|
+
});
|
|
1134
|
+
const taskEndEventId = await appendLocalTaskEvent({
|
|
1135
|
+
agentName: deps.agentName,
|
|
1136
|
+
workdir,
|
|
1137
|
+
conversationId: message.conversationId,
|
|
1138
|
+
taskId,
|
|
1139
|
+
phase: "completed",
|
|
1140
|
+
stage: "done",
|
|
1141
|
+
status: "succeeded",
|
|
1142
|
+
route: "core_cm",
|
|
1143
|
+
summary: summarizeCoreCmTaskOutcome({
|
|
1144
|
+
peripheralEventIds,
|
|
1145
|
+
cmEventIds,
|
|
1146
|
+
memoryProposal: result.memoryProposal,
|
|
1147
|
+
memoryAction: result.memoryAction,
|
|
1148
|
+
}),
|
|
1149
|
+
refs: [{
|
|
1150
|
+
eventIds: [...taskEventIds, ...dispatchCmEventIds, ...mainChatEventIds, ...peripheralEventIds, ...cmEventIds, ...reviewCmEventIds, ...computeEventIds],
|
|
1151
|
+
conversationId: message.conversationId,
|
|
1152
|
+
messageId: response.id,
|
|
1153
|
+
taskId,
|
|
1154
|
+
...(result.memoryProposal?.id ? { memoryProposalId: result.memoryProposal.id } : {}),
|
|
1155
|
+
}],
|
|
1156
|
+
data: {
|
|
1157
|
+
outputChars: output.length,
|
|
1158
|
+
peripheralEventCount: peripheralEventIds.length,
|
|
1159
|
+
cmEventCount: dispatchCmEventIds.length + cmEventIds.length + reviewCmEventIds.length,
|
|
1160
|
+
computeEventCount: computeEventIds.length,
|
|
1161
|
+
latestReviewQuality: reviewRecord.resultQuality,
|
|
1162
|
+
latestCompletionDecision: reviewRecord.completionDecision,
|
|
1163
|
+
followUpNeeded: reviewRecord.followUpNeeded,
|
|
1164
|
+
...(result.memoryProposal?.id ? { memoryProposalId: result.memoryProposal.id } : {}),
|
|
1165
|
+
...(result.memoryAction ? { memoryAction: createMemoryActionTaskData(result.memoryAction, "core_cm") } : {}),
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
if (taskEndEventId)
|
|
1169
|
+
taskEventIds.push(taskEndEventId);
|
|
1170
|
+
const task = await readLocalTaskResponseRecord({ workdir, agentName: deps.agentName, taskId });
|
|
1171
|
+
const eventIds = createLocalChatEventIds({
|
|
1172
|
+
mainChat: mainChatEventIds,
|
|
1173
|
+
peripheral: peripheralEventIds,
|
|
1174
|
+
cm: [...dispatchCmEventIds, ...cmEventIds, ...reviewCmEventIds],
|
|
1175
|
+
compute: computeEventIds,
|
|
1176
|
+
task: taskEventIds,
|
|
1177
|
+
});
|
|
1178
|
+
const cmItems = createCoreCmProcessItems(result.processNotes, eventIds);
|
|
1179
|
+
if (!responseSent) {
|
|
1180
|
+
writeJsonResponse(res, 200, {
|
|
1181
|
+
success: true,
|
|
1182
|
+
message,
|
|
1183
|
+
response,
|
|
1184
|
+
output,
|
|
1185
|
+
task: createOptionalTaskResponseRecord(task),
|
|
1186
|
+
taskView: createTaskViewResponse(task),
|
|
1187
|
+
memoryProposal: result.memoryProposal || null,
|
|
1188
|
+
eventIds,
|
|
1189
|
+
cmItems,
|
|
1190
|
+
}, true);
|
|
1191
|
+
}
|
|
1192
|
+
if (deps.passiveReflection) {
|
|
1193
|
+
const passiveReflectionEventIds = createLocalChatEventIds({
|
|
1194
|
+
mainChat: mainChatEventIds,
|
|
1195
|
+
peripheral: peripheralEventIds,
|
|
1196
|
+
cm: result.memoryProposal || result.memoryAction ? cmEventIds : [],
|
|
1197
|
+
compute: computeEventIds,
|
|
1198
|
+
task: taskEventIds,
|
|
1199
|
+
});
|
|
1200
|
+
schedulePassiveReflection({
|
|
1201
|
+
agentName: deps.agentName,
|
|
1202
|
+
workdir: deps.workdir || process.cwd(),
|
|
1203
|
+
conversationId: message.conversationId,
|
|
1204
|
+
eventIds: passiveReflectionEventIds,
|
|
1205
|
+
requestCompute: deps.requestCompute,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const output = await submitLocalAkemonMessageToMcp({
|
|
1211
|
+
localPort: deps.localPort,
|
|
1212
|
+
message,
|
|
1213
|
+
});
|
|
1214
|
+
const response = createLocalAkemonResponseMessage({
|
|
1215
|
+
request: message,
|
|
1216
|
+
responderAgent: deps.agentName,
|
|
1217
|
+
result: output,
|
|
249
1218
|
});
|
|
1219
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
1220
|
+
agentName: deps.agentName,
|
|
1221
|
+
workdir: deps.workdir || process.cwd(),
|
|
1222
|
+
conversationId: message.conversationId,
|
|
1223
|
+
ownerMessageId: message.id,
|
|
1224
|
+
akemonMessageId: response.id,
|
|
1225
|
+
userText: text,
|
|
1226
|
+
agentText: output,
|
|
1227
|
+
});
|
|
1228
|
+
writeJsonResponse(res, 200, {
|
|
1229
|
+
success: true,
|
|
1230
|
+
message,
|
|
1231
|
+
response,
|
|
1232
|
+
output,
|
|
1233
|
+
eventIds: createLocalChatEventIds({ mainChat: mainChatEventIds }),
|
|
1234
|
+
}, true);
|
|
250
1235
|
}
|
|
251
1236
|
catch (err) {
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1237
|
+
if (responseSent && acceptedAsyncTask) {
|
|
1238
|
+
await appendLocalTaskEvent({
|
|
1239
|
+
agentName: deps.agentName,
|
|
1240
|
+
workdir: acceptedAsyncTask.workdir,
|
|
1241
|
+
conversationId: acceptedAsyncTask.conversationId,
|
|
1242
|
+
taskId: acceptedAsyncTask.taskId,
|
|
1243
|
+
phase: "failed",
|
|
1244
|
+
stage: "blocked",
|
|
1245
|
+
status: "failed",
|
|
1246
|
+
route: "core_cm",
|
|
1247
|
+
summary: err.message || "Core CM task failed after async acceptance.",
|
|
1248
|
+
refs: [{
|
|
1249
|
+
conversationId: acceptedAsyncTask.conversationId,
|
|
1250
|
+
messageId: acceptedAsyncTask.messageId,
|
|
1251
|
+
taskId: acceptedAsyncTask.taskId,
|
|
1252
|
+
}],
|
|
1253
|
+
data: {
|
|
1254
|
+
source: "local_chat",
|
|
1255
|
+
acceptedAsync: true,
|
|
1256
|
+
error: err.message || String(err),
|
|
1257
|
+
},
|
|
1258
|
+
});
|
|
256
1259
|
return;
|
|
257
1260
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1261
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid local Akemon message" });
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
async function appendLocalChatRound(input) {
|
|
1265
|
+
const eventIds = [];
|
|
1266
|
+
if (!input.conversationId)
|
|
1267
|
+
return eventIds;
|
|
1268
|
+
try {
|
|
1269
|
+
await appendRound(input.workdir, input.agentName, input.conversationId, input.userText, input.agentText);
|
|
1270
|
+
}
|
|
1271
|
+
catch (error) {
|
|
1272
|
+
logBestEffortError("local chat conversation append", error);
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
const ownerEvent = await appendCognitiveEvent({
|
|
1276
|
+
workdir: input.workdir,
|
|
1277
|
+
agentName: input.agentName,
|
|
1278
|
+
event: {
|
|
1279
|
+
stream: "main_chat",
|
|
1280
|
+
taskId: input.taskId,
|
|
1281
|
+
conversationId: input.conversationId,
|
|
1282
|
+
role: "owner",
|
|
1283
|
+
text: input.userText,
|
|
1284
|
+
messageId: input.ownerMessageId,
|
|
1285
|
+
refs: [
|
|
1286
|
+
{
|
|
1287
|
+
conversationId: input.conversationId,
|
|
1288
|
+
messageId: input.ownerMessageId,
|
|
1289
|
+
taskId: input.taskId,
|
|
1290
|
+
},
|
|
1291
|
+
],
|
|
1292
|
+
},
|
|
1293
|
+
});
|
|
1294
|
+
eventIds.push(ownerEvent.id);
|
|
1295
|
+
const akemonEvent = await appendCognitiveEvent({
|
|
1296
|
+
workdir: input.workdir,
|
|
1297
|
+
agentName: input.agentName,
|
|
1298
|
+
event: {
|
|
1299
|
+
stream: "main_chat",
|
|
1300
|
+
taskId: input.taskId,
|
|
1301
|
+
conversationId: input.conversationId,
|
|
1302
|
+
role: "akemon",
|
|
1303
|
+
text: input.agentText,
|
|
1304
|
+
messageId: input.akemonMessageId,
|
|
1305
|
+
refs: [
|
|
1306
|
+
{
|
|
1307
|
+
conversationId: input.conversationId,
|
|
1308
|
+
messageId: input.akemonMessageId,
|
|
1309
|
+
taskId: input.taskId,
|
|
1310
|
+
},
|
|
1311
|
+
],
|
|
1312
|
+
},
|
|
261
1313
|
});
|
|
1314
|
+
eventIds.push(akemonEvent.id);
|
|
262
1315
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (streamStarted && !res.writableEnded)
|
|
266
|
-
res.end();
|
|
1316
|
+
catch (error) {
|
|
1317
|
+
logBestEffortError("local chat event append", error);
|
|
267
1318
|
}
|
|
1319
|
+
return eventIds;
|
|
268
1320
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
1321
|
+
function createLocalChatEventIds(input) {
|
|
1322
|
+
return {
|
|
1323
|
+
mainChat: input.mainChat || [],
|
|
1324
|
+
peripheral: input.peripheral || [],
|
|
1325
|
+
cm: input.cm || [],
|
|
1326
|
+
compute: input.compute || [],
|
|
1327
|
+
task: input.task || [],
|
|
1328
|
+
};
|
|
276
1329
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
1330
|
+
function createCoreCmProcessItems(processNotes, eventIds) {
|
|
1331
|
+
return (processNotes || [])
|
|
1332
|
+
.map((note) => (typeof note === "string" ? note.trim() : ""))
|
|
1333
|
+
.filter(Boolean)
|
|
1334
|
+
.map((text) => ({
|
|
1335
|
+
type: "process",
|
|
1336
|
+
title: "Core CM",
|
|
1337
|
+
text,
|
|
1338
|
+
status: "info",
|
|
1339
|
+
eventIds,
|
|
1340
|
+
}));
|
|
1341
|
+
}
|
|
1342
|
+
function createLocalChatTaskId(message) {
|
|
1343
|
+
const suffix = message.id || `${message.conversationId || "local"}_${Date.now()}`;
|
|
1344
|
+
return `task_${suffix.replace(/[^a-zA-Z0-9_.:-]/g, "_")}`;
|
|
1345
|
+
}
|
|
1346
|
+
function readLocalChatFollowUpTaskId(message) {
|
|
1347
|
+
const value = message.metadata?.followUpForTaskId;
|
|
1348
|
+
if (typeof value !== "string")
|
|
1349
|
+
return null;
|
|
1350
|
+
const taskId = value.trim();
|
|
1351
|
+
if (!taskId || taskId.includes("/") || taskId.length > 220)
|
|
1352
|
+
return null;
|
|
1353
|
+
return taskId;
|
|
1354
|
+
}
|
|
1355
|
+
async function handleLocalChatFollowUpHttp(input) {
|
|
1356
|
+
const existing = await readTaskRegistryRecord({
|
|
1357
|
+
workdir: input.workdir,
|
|
1358
|
+
agentName: input.agentName,
|
|
1359
|
+
taskId: input.taskId,
|
|
1360
|
+
});
|
|
1361
|
+
if (existing && (existing.stage === "done" || existing.stage === "blocked")) {
|
|
1362
|
+
writeJsonResponse(input.res, 409, { error: `Task ${input.taskId} is already ${existing.stage}` });
|
|
287
1363
|
return;
|
|
288
1364
|
}
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
1365
|
+
if (existing && isExpiredLocalChatFollowUpTarget(existing)) {
|
|
1366
|
+
await appendLocalTaskEvent({
|
|
1367
|
+
agentName: input.agentName,
|
|
1368
|
+
workdir: input.workdir,
|
|
1369
|
+
conversationId: existing.conversationId || input.message.conversationId,
|
|
1370
|
+
taskId: input.taskId,
|
|
1371
|
+
phase: "failed",
|
|
1372
|
+
stage: "blocked",
|
|
1373
|
+
status: "failed",
|
|
1374
|
+
route: existing.route || "core_cm",
|
|
1375
|
+
summary: "Akemon stopped recovering a stale local chat task.",
|
|
1376
|
+
refs: [{
|
|
1377
|
+
conversationId: input.message.conversationId,
|
|
1378
|
+
messageId: input.message.id,
|
|
1379
|
+
taskId: input.taskId,
|
|
1380
|
+
}],
|
|
1381
|
+
data: {
|
|
1382
|
+
source: "local_chat_stale_recovery",
|
|
1383
|
+
staleTaskCreatedAt: existing.createdAt,
|
|
1384
|
+
staleTaskUpdatedAt: existing.updatedAt,
|
|
1385
|
+
},
|
|
1386
|
+
});
|
|
1387
|
+
writeJsonResponse(input.res, 409, {
|
|
1388
|
+
error: `Task ${input.taskId} is stale and can no longer accept follow-ups. Send a new message instead.`,
|
|
1389
|
+
});
|
|
301
1390
|
return;
|
|
302
1391
|
}
|
|
303
|
-
|
|
1392
|
+
const followUp = createLocalChatFollowUp(input.text);
|
|
1393
|
+
const followUps = appendTaskFollowUp(existing, followUp);
|
|
1394
|
+
const output = formatLocalChatFollowUpAck(input.text, followUp.kind);
|
|
1395
|
+
const response = createLocalAkemonResponseMessage({
|
|
1396
|
+
request: input.message,
|
|
1397
|
+
responderAgent: input.agentName,
|
|
1398
|
+
result: output,
|
|
1399
|
+
});
|
|
1400
|
+
const taskEventIds = [];
|
|
1401
|
+
const mainChatEventIds = await appendLocalChatRound({
|
|
1402
|
+
agentName: input.agentName,
|
|
1403
|
+
workdir: input.workdir,
|
|
1404
|
+
conversationId: input.message.conversationId,
|
|
1405
|
+
ownerMessageId: input.message.id,
|
|
1406
|
+
akemonMessageId: response.id,
|
|
1407
|
+
taskId: input.taskId,
|
|
1408
|
+
userText: input.text,
|
|
1409
|
+
agentText: output,
|
|
1410
|
+
});
|
|
1411
|
+
await appendLocalTaskStageEvent({
|
|
1412
|
+
taskEventIds,
|
|
1413
|
+
agentName: input.agentName,
|
|
1414
|
+
workdir: input.workdir,
|
|
1415
|
+
conversationId: input.message.conversationId,
|
|
1416
|
+
taskId: input.taskId,
|
|
1417
|
+
phase: "received",
|
|
1418
|
+
stage: existing?.stage || "queued",
|
|
1419
|
+
status: existing?.status && existing.status !== "succeeded" && existing.status !== "failed"
|
|
1420
|
+
? existing.status
|
|
1421
|
+
: "running",
|
|
1422
|
+
route: existing?.route || "core_cm",
|
|
1423
|
+
summary: `Owner ${followUp.kind} follow-up queued.`,
|
|
1424
|
+
refs: [{
|
|
1425
|
+
eventIds: mainChatEventIds,
|
|
1426
|
+
conversationId: input.message.conversationId,
|
|
1427
|
+
messageId: input.message.id,
|
|
1428
|
+
taskId: input.taskId,
|
|
1429
|
+
}],
|
|
1430
|
+
data: {
|
|
1431
|
+
source: "local_chat_follow_up",
|
|
1432
|
+
followUp,
|
|
1433
|
+
followUps,
|
|
1434
|
+
followUpCount: followUps.length,
|
|
1435
|
+
latestFollowUpKind: followUp.kind,
|
|
1436
|
+
latestFollowUpText: followUp.text,
|
|
1437
|
+
},
|
|
1438
|
+
});
|
|
1439
|
+
const task = await readLocalTaskResponseRecord({
|
|
1440
|
+
workdir: input.workdir,
|
|
1441
|
+
agentName: input.agentName,
|
|
1442
|
+
taskId: input.taskId,
|
|
1443
|
+
});
|
|
1444
|
+
writeJsonResponse(input.res, 200, {
|
|
1445
|
+
success: true,
|
|
1446
|
+
queued: true,
|
|
1447
|
+
message: input.message,
|
|
1448
|
+
response,
|
|
1449
|
+
output,
|
|
1450
|
+
followUp,
|
|
1451
|
+
task: createOptionalTaskResponseRecord(task),
|
|
1452
|
+
taskView: createTaskViewResponse(task),
|
|
1453
|
+
eventIds: createLocalChatEventIds({
|
|
1454
|
+
mainChat: mainChatEventIds,
|
|
1455
|
+
task: taskEventIds,
|
|
1456
|
+
}),
|
|
1457
|
+
}, true);
|
|
304
1458
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
1459
|
+
function isExpiredLocalChatFollowUpTarget(task) {
|
|
1460
|
+
if (task.route !== "core_cm")
|
|
1461
|
+
return false;
|
|
1462
|
+
if (task.stage === "done" || task.stage === "blocked")
|
|
1463
|
+
return false;
|
|
1464
|
+
if (task.status === "succeeded" || task.status === "failed" || task.status === "info")
|
|
1465
|
+
return false;
|
|
1466
|
+
const createdAt = Date.parse(task.createdAt || "");
|
|
1467
|
+
if (!Number.isFinite(createdAt))
|
|
1468
|
+
return false;
|
|
1469
|
+
return Date.now() - createdAt > LOCAL_CHAT_FOLLOW_UP_TARGET_MAX_AGE_MS;
|
|
307
1470
|
}
|
|
308
|
-
function
|
|
309
|
-
return
|
|
1471
|
+
function createLocalChatFollowUp(text) {
|
|
1472
|
+
return {
|
|
1473
|
+
id: createCognitiveEventId("followup"),
|
|
1474
|
+
kind: classifyLocalChatFollowUp(text),
|
|
1475
|
+
text: truncateEventText(text.trim(), 1000),
|
|
1476
|
+
createdAt: new Date().toISOString(),
|
|
1477
|
+
status: "queued",
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function appendTaskFollowUp(existing, followUp) {
|
|
1481
|
+
const current = Array.isArray(existing?.data?.followUps)
|
|
1482
|
+
? existing.data.followUps
|
|
1483
|
+
.map(normalizeTaskFollowUp)
|
|
1484
|
+
.filter((item) => !!item)
|
|
1485
|
+
: [];
|
|
1486
|
+
return [...current, followUp].slice(-20);
|
|
1487
|
+
}
|
|
1488
|
+
function normalizeTaskFollowUp(value) {
|
|
1489
|
+
if (!value || typeof value !== "object")
|
|
1490
|
+
return null;
|
|
1491
|
+
const item = value;
|
|
1492
|
+
const text = typeof item.text === "string" ? item.text.trim() : "";
|
|
1493
|
+
if (!text)
|
|
1494
|
+
return null;
|
|
1495
|
+
return {
|
|
1496
|
+
id: typeof item.id === "string" && item.id ? item.id : createCognitiveEventId("followup"),
|
|
1497
|
+
kind: isLocalChatFollowUpKind(item.kind) ? item.kind : "note",
|
|
1498
|
+
text: truncateEventText(text, 1000),
|
|
1499
|
+
createdAt: typeof item.createdAt === "string" && item.createdAt ? item.createdAt : new Date().toISOString(),
|
|
1500
|
+
status: isLocalChatFollowUpStatus(item.status) ? item.status : "queued",
|
|
1501
|
+
absorbedAt: typeof item.absorbedAt === "string" && item.absorbedAt ? item.absorbedAt : undefined,
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function classifyLocalChatFollowUp(text) {
|
|
1505
|
+
const normalized = text.trim().toLowerCase();
|
|
1506
|
+
if (/^(correction|actually|instead|wait|no,|not that)\b/.test(normalized)
|
|
1507
|
+
|| /^(不是|不对|等等|等下|更正|纠正|改成)/.test(text.trim())) {
|
|
1508
|
+
return "correction";
|
|
1509
|
+
}
|
|
1510
|
+
if (/(constraint|requirement|must|must not|do not|don't|only|never)\b/.test(normalized)
|
|
1511
|
+
|| /(不要|不能|必须|只能|不允许|限制|约束)/.test(text)) {
|
|
1512
|
+
return "constraint";
|
|
1513
|
+
}
|
|
1514
|
+
if (/[??]\s*$/.test(text)
|
|
1515
|
+
|| /^(why|how|what|when|where|can|could|should|will|is|are)\b/.test(normalized)
|
|
1516
|
+
|| /^(为什么|怎么|如何|是否|能不能|可以|会不会)/.test(text.trim())) {
|
|
1517
|
+
return "question";
|
|
1518
|
+
}
|
|
1519
|
+
return "note";
|
|
1520
|
+
}
|
|
1521
|
+
function isLocalChatFollowUpKind(value) {
|
|
1522
|
+
return value === "correction" || value === "constraint" || value === "question" || value === "note";
|
|
1523
|
+
}
|
|
1524
|
+
function isLocalChatFollowUpStatus(value) {
|
|
1525
|
+
return value === "queued" || value === "absorbed";
|
|
1526
|
+
}
|
|
1527
|
+
function formatLocalChatFollowUpAck(text, kind) {
|
|
1528
|
+
if (shouldUseChineseOwnerText(undefined, text)) {
|
|
1529
|
+
const labels = {
|
|
1530
|
+
correction: "修正",
|
|
1531
|
+
constraint: "约束",
|
|
1532
|
+
question: "问题",
|
|
1533
|
+
note: "补充",
|
|
1534
|
+
};
|
|
1535
|
+
return `已把这条${labels[kind]}加入当前任务队列。`;
|
|
1536
|
+
}
|
|
1537
|
+
return `Queued this ${kind} for the current task.`;
|
|
1538
|
+
}
|
|
1539
|
+
async function readQueuedActionableFollowUps(input) {
|
|
1540
|
+
const task = await readTaskRegistryRecord(input);
|
|
1541
|
+
if (!Array.isArray(task?.data?.followUps))
|
|
1542
|
+
return [];
|
|
1543
|
+
return task.data.followUps
|
|
1544
|
+
.map(normalizeTaskFollowUp)
|
|
1545
|
+
.filter((item) => !!item
|
|
1546
|
+
&& item.status !== "absorbed"
|
|
1547
|
+
&& isActionableQueuedFollowUpKind(item.kind));
|
|
1548
|
+
}
|
|
1549
|
+
function createAbsorbedFollowUpTaskData(existing, absorbedFollowUps) {
|
|
1550
|
+
const absorbedIds = new Set(absorbedFollowUps.map((followUp) => followUp.id).filter(Boolean));
|
|
1551
|
+
if (!absorbedIds.size)
|
|
1552
|
+
return {};
|
|
1553
|
+
const now = new Date().toISOString();
|
|
1554
|
+
const currentFollowUps = Array.isArray(existing?.data?.followUps)
|
|
1555
|
+
? existing.data.followUps
|
|
1556
|
+
.map(normalizeTaskFollowUp)
|
|
1557
|
+
.filter((item) => !!item)
|
|
1558
|
+
: absorbedFollowUps;
|
|
1559
|
+
const followUps = currentFollowUps.map((followUp) => absorbedIds.has(followUp.id)
|
|
1560
|
+
? {
|
|
1561
|
+
...followUp,
|
|
1562
|
+
status: "absorbed",
|
|
1563
|
+
absorbedAt: followUp.absorbedAt || now,
|
|
1564
|
+
}
|
|
1565
|
+
: followUp);
|
|
1566
|
+
const absorbedFollowUpIds = followUps
|
|
1567
|
+
.filter((followUp) => followUp.status === "absorbed")
|
|
1568
|
+
.map((followUp) => followUp.id);
|
|
1569
|
+
return {
|
|
1570
|
+
followUps,
|
|
1571
|
+
followUpCount: followUps.length,
|
|
1572
|
+
pendingFollowUpCount: followUps.filter((followUp) => followUp.status !== "absorbed").length,
|
|
1573
|
+
absorbedFollowUpCount: absorbedFollowUpIds.length,
|
|
1574
|
+
absorbedFollowUpIds,
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function isActionableQueuedFollowUpKind(kind) {
|
|
1578
|
+
return kind === "question" || kind === "constraint" || kind === "correction";
|
|
1579
|
+
}
|
|
1580
|
+
function formatCoreCmQueuedFollowUpText(input) {
|
|
1581
|
+
const lines = [
|
|
1582
|
+
"Queued owner follow-up(s) arrived while Akemon was handling the current task.",
|
|
1583
|
+
"",
|
|
1584
|
+
"Original owner request:",
|
|
1585
|
+
input.originalText,
|
|
1586
|
+
"",
|
|
1587
|
+
"Current Akemon result for the original request:",
|
|
1588
|
+
input.currentOutput || "(no result yet)",
|
|
1589
|
+
"",
|
|
1590
|
+
"Queued owner follow-up(s) to incorporate now:",
|
|
1591
|
+
...input.followUps.map((followUp, index) => `${index + 1}. [${followUp.kind}] ${followUp.text}`),
|
|
1592
|
+
"",
|
|
1593
|
+
"Before completing the task, produce the final owner-facing report.",
|
|
1594
|
+
"If a follow-up is a constraint, apply it when possible and state whether it changed the final answer or next action.",
|
|
1595
|
+
"If a follow-up is a correction, revise the final answer when possible and state whether the earlier result needed correction.",
|
|
1596
|
+
"If a follow-up is a question, answer it directly using the current result and available context.",
|
|
1597
|
+
"If the task already performed an action that cannot be undone, say that clearly and propose the safest next step.",
|
|
1598
|
+
];
|
|
1599
|
+
return lines.join("\n");
|
|
1600
|
+
}
|
|
1601
|
+
function mergeCoreCmQueuedFollowUpResult(input) {
|
|
1602
|
+
return {
|
|
1603
|
+
output: mergeCoreCmQueuedFollowUpOutput(input),
|
|
1604
|
+
processNotes: [
|
|
1605
|
+
...(input.base.processNotes || []),
|
|
1606
|
+
...(input.followUp.processNotes || []),
|
|
1607
|
+
],
|
|
1608
|
+
memoryProposal: input.followUp.memoryProposal || input.base.memoryProposal,
|
|
1609
|
+
memoryAction: input.followUp.memoryAction || input.base.memoryAction,
|
|
1610
|
+
review: input.followUp.review || input.base.review,
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function mergeCoreCmQueuedFollowUpOutput(input) {
|
|
1614
|
+
const baseOutput = input.base.output.trim();
|
|
1615
|
+
const followUpOutput = input.followUp.output.trim();
|
|
1616
|
+
if (!baseOutput)
|
|
1617
|
+
return followUpOutput;
|
|
1618
|
+
if (!followUpOutput)
|
|
1619
|
+
return baseOutput;
|
|
1620
|
+
const ownerText = input.followUps.map((followUp) => followUp.text).join("\n");
|
|
1621
|
+
const includesQuestion = input.followUps.some((followUp) => followUp.kind === "question");
|
|
1622
|
+
const includesRevision = input.followUps.some((followUp) => followUp.kind === "constraint" || followUp.kind === "correction");
|
|
1623
|
+
if (includesQuestion && includesRevision) {
|
|
1624
|
+
const mixedLabel = shouldUseChineseOwnerText(input.ownerLanguage, ownerText)
|
|
1625
|
+
? "关于你排队的后续问题、约束和修正:"
|
|
1626
|
+
: "Queued follow-up answer with applied correction/constraint:";
|
|
1627
|
+
return `${baseOutput}\n\n${mixedLabel}\n${followUpOutput}`;
|
|
1628
|
+
}
|
|
1629
|
+
if (includesRevision) {
|
|
1630
|
+
const revisionLabel = shouldUseChineseOwnerText(input.ownerLanguage, ownerText)
|
|
1631
|
+
? "已根据你排队的修正/约束更新最终报告:"
|
|
1632
|
+
: "Updated final report after queued correction/constraint:";
|
|
1633
|
+
return `${revisionLabel}\n${followUpOutput}`;
|
|
1634
|
+
}
|
|
1635
|
+
const label = shouldUseChineseOwnerText(input.ownerLanguage, ownerText)
|
|
1636
|
+
? "关于你排队的后续问题:"
|
|
1637
|
+
: "Queued follow-up answer:";
|
|
1638
|
+
return `${baseOutput}\n\n${label}\n${followUpOutput}`;
|
|
1639
|
+
}
|
|
1640
|
+
function shouldUseChineseOwnerText(_ownerLanguage, ownerText) {
|
|
1641
|
+
return /[\u3400-\u9fff]/.test(ownerText || "");
|
|
1642
|
+
}
|
|
1643
|
+
function createCoreCmDispatchRecord(input) {
|
|
1644
|
+
return {
|
|
1645
|
+
id: createCognitiveEventId("dispatch"),
|
|
1646
|
+
createdAt: new Date().toISOString(),
|
|
1647
|
+
taskId: input.taskId,
|
|
1648
|
+
conversationId: input.conversationId,
|
|
1649
|
+
targetPeripheral: input.proposal.peripheral,
|
|
1650
|
+
capability: input.proposal.capability,
|
|
1651
|
+
brief: coreCmDispatchBrief(input.proposal, input.ownerText),
|
|
1652
|
+
contextBoundary: {
|
|
1653
|
+
includes: [
|
|
1654
|
+
"owner request",
|
|
1655
|
+
"current interactive-session context",
|
|
1656
|
+
"relevant Akemon memory summary",
|
|
1657
|
+
],
|
|
1658
|
+
excludes: [
|
|
1659
|
+
"unbounded owner context",
|
|
1660
|
+
"direct Akemon self-memory writes",
|
|
1661
|
+
"peripheral-owned private state outside the requested capability",
|
|
1662
|
+
],
|
|
1663
|
+
},
|
|
1664
|
+
permissionBoundary: {
|
|
1665
|
+
allowed: [`interactive-session.${input.proposal.capability}`],
|
|
1666
|
+
forbidden: [
|
|
1667
|
+
"memory writes without a Memory CM proposal",
|
|
1668
|
+
"actions outside the selected interactive-session capability",
|
|
1669
|
+
],
|
|
1670
|
+
requiresOwnerApproval: false,
|
|
1671
|
+
},
|
|
1672
|
+
expectedDeliverable: expectedInteractiveSessionDeliverable(input.proposal.capability),
|
|
1673
|
+
reason: input.proposal.reason,
|
|
1674
|
+
argsSummary: summarizeInteractiveSessionDispatchArgs(input.proposal),
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function coreCmDispatchBrief(proposal, ownerText) {
|
|
1678
|
+
if (proposal.reason?.trim())
|
|
1679
|
+
return truncateEventText(proposal.reason.trim(), 260);
|
|
1680
|
+
const target = proposal.peripheral === "workbench" ? "interactive-session" : proposal.peripheral;
|
|
1681
|
+
const ownerBrief = truncateEventText(ownerText.trim(), 140);
|
|
1682
|
+
return ownerBrief
|
|
1683
|
+
? `Use ${target}.${proposal.capability} for: ${ownerBrief}`
|
|
1684
|
+
: `Use ${target}.${proposal.capability}.`;
|
|
1685
|
+
}
|
|
1686
|
+
function expectedInteractiveSessionDeliverable(capability) {
|
|
1687
|
+
switch (capability) {
|
|
1688
|
+
case "list_sessions":
|
|
1689
|
+
return "List available interactive sessions and their owner-relevant state.";
|
|
1690
|
+
case "inspect_session":
|
|
1691
|
+
case "capture_output":
|
|
1692
|
+
return "Return session status and recent output relevant to the owner request.";
|
|
1693
|
+
case "start_session":
|
|
1694
|
+
return "Return started session metadata and how the owner can observe or control it.";
|
|
1695
|
+
case "send_input":
|
|
1696
|
+
return "Return input delivery status and any observed output after the requested wait.";
|
|
1697
|
+
case "set_input_mode":
|
|
1698
|
+
return "Return confirmation of the session input mode change.";
|
|
1699
|
+
case "stop_session":
|
|
1700
|
+
return "Return confirmation that the session was asked to stop and current session state.";
|
|
1701
|
+
case "resize_session":
|
|
1702
|
+
return "Return confirmation of the terminal size update.";
|
|
1703
|
+
default:
|
|
1704
|
+
return "Return the peripheral observation needed for Core CM review.";
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function summarizeInteractiveSessionDispatchArgs(proposal) {
|
|
1708
|
+
const args = proposal.args || {};
|
|
1709
|
+
const summary = {};
|
|
1710
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1711
|
+
if (key === "input" && typeof value === "string") {
|
|
1712
|
+
summary.inputChars = value.length;
|
|
1713
|
+
summary.inputPreview = truncateEventText(value.replace(/\s+/g, " ").trim(), 120);
|
|
1714
|
+
}
|
|
1715
|
+
else if (key === "args" && Array.isArray(value)) {
|
|
1716
|
+
summary.argsCount = value.length;
|
|
1717
|
+
}
|
|
1718
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1719
|
+
summary[key] = value;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return Object.keys(summary).length ? summary : undefined;
|
|
1723
|
+
}
|
|
1724
|
+
function appendTaskDispatchRecord(existing, dispatchRecord) {
|
|
1725
|
+
const current = Array.isArray(existing?.data?.dispatchRecords)
|
|
1726
|
+
? existing.data.dispatchRecords
|
|
1727
|
+
.map(normalizeCoreCmDispatchRecord)
|
|
1728
|
+
.filter((item) => !!item)
|
|
1729
|
+
: [];
|
|
1730
|
+
return [...current, dispatchRecord].slice(-20);
|
|
1731
|
+
}
|
|
1732
|
+
function normalizeCoreCmDispatchRecord(value) {
|
|
1733
|
+
if (!value || typeof value !== "object")
|
|
1734
|
+
return null;
|
|
1735
|
+
const record = value;
|
|
1736
|
+
if (typeof record.id !== "string" || typeof record.targetPeripheral !== "string" || typeof record.capability !== "string")
|
|
1737
|
+
return null;
|
|
1738
|
+
return {
|
|
1739
|
+
id: record.id,
|
|
1740
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : new Date().toISOString(),
|
|
1741
|
+
taskId: typeof record.taskId === "string" ? record.taskId : "",
|
|
1742
|
+
conversationId: typeof record.conversationId === "string" ? record.conversationId : undefined,
|
|
1743
|
+
targetPeripheral: record.targetPeripheral,
|
|
1744
|
+
capability: record.capability,
|
|
1745
|
+
brief: typeof record.brief === "string" ? record.brief : `${record.targetPeripheral}.${record.capability}`,
|
|
1746
|
+
contextBoundary: normalizeDispatchBoundary(record.contextBoundary),
|
|
1747
|
+
permissionBoundary: normalizePermissionBoundary(record.permissionBoundary),
|
|
1748
|
+
expectedDeliverable: typeof record.expectedDeliverable === "string" ? record.expectedDeliverable : "Return a peripheral observation.",
|
|
1749
|
+
reason: typeof record.reason === "string" ? record.reason : undefined,
|
|
1750
|
+
argsSummary: record.argsSummary && typeof record.argsSummary === "object" && !Array.isArray(record.argsSummary)
|
|
1751
|
+
? record.argsSummary
|
|
1752
|
+
: undefined,
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function normalizeDispatchBoundary(value) {
|
|
1756
|
+
const record = value && typeof value === "object" ? value : {};
|
|
1757
|
+
return {
|
|
1758
|
+
includes: Array.isArray(record.includes) ? record.includes.filter((item) => typeof item === "string") : [],
|
|
1759
|
+
excludes: Array.isArray(record.excludes) ? record.excludes.filter((item) => typeof item === "string") : [],
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function normalizePermissionBoundary(value) {
|
|
1763
|
+
const record = value && typeof value === "object" ? value : {};
|
|
1764
|
+
return {
|
|
1765
|
+
allowed: Array.isArray(record.allowed) ? record.allowed.filter((item) => typeof item === "string") : [],
|
|
1766
|
+
forbidden: Array.isArray(record.forbidden) ? record.forbidden.filter((item) => typeof item === "string") : [],
|
|
1767
|
+
requiresOwnerApproval: record.requiresOwnerApproval === true,
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
async function appendCoreCmDispatchRecordEvent(input) {
|
|
1771
|
+
if (!input.conversationId)
|
|
1772
|
+
return null;
|
|
1773
|
+
try {
|
|
1774
|
+
const event = await appendCognitiveEvent({
|
|
1775
|
+
workdir: input.workdir,
|
|
1776
|
+
agentName: input.agentName,
|
|
1777
|
+
event: {
|
|
1778
|
+
stream: "cm",
|
|
1779
|
+
taskId: input.taskId,
|
|
1780
|
+
conversationId: input.conversationId,
|
|
1781
|
+
cm: "core",
|
|
1782
|
+
itemType: "dispatch_record",
|
|
1783
|
+
status: "info",
|
|
1784
|
+
summary: `Core CM dispatched ${input.dispatchRecord.capability} to ${input.dispatchRecord.targetPeripheral}.`,
|
|
1785
|
+
data: {
|
|
1786
|
+
dispatchRecord: input.dispatchRecord,
|
|
1787
|
+
},
|
|
1788
|
+
refs: [{
|
|
1789
|
+
eventIds: input.sourceEventIds,
|
|
1790
|
+
conversationId: input.conversationId,
|
|
1791
|
+
messageId: input.ownerMessageId,
|
|
1792
|
+
taskId: input.taskId,
|
|
1793
|
+
}],
|
|
1794
|
+
},
|
|
1795
|
+
});
|
|
1796
|
+
return event.id;
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
logBestEffortError("Core CM dispatch event append", error);
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function createCoreCmReviewRecord(input) {
|
|
1804
|
+
const review = input.result.review;
|
|
1805
|
+
const completionDecision = normalizeCoreCmCompletionDecision(review?.completionDecision)
|
|
1806
|
+
|| (input.output.trim() ? "complete" : "blocked");
|
|
1807
|
+
const resultQuality = normalizeCoreCmResultQuality(review?.resultQuality)
|
|
1808
|
+
|| (completionDecision === "blocked" ? "failed" : "good");
|
|
1809
|
+
const followUpNeeded = typeof review?.followUpNeeded === "boolean"
|
|
1810
|
+
? review.followUpNeeded
|
|
1811
|
+
: completionDecision !== "complete";
|
|
1812
|
+
const memoryProposalRefs = createCoreCmReviewMemoryProposalRefs(input.result);
|
|
1813
|
+
return {
|
|
1814
|
+
id: createCognitiveEventId("review"),
|
|
1815
|
+
createdAt: new Date().toISOString(),
|
|
1816
|
+
taskId: input.taskId,
|
|
1817
|
+
conversationId: input.conversationId,
|
|
1818
|
+
source: review ? "core_cm_observation_review" : "core_cm_task_report",
|
|
1819
|
+
resultQuality,
|
|
1820
|
+
completionDecision,
|
|
1821
|
+
followUpNeeded,
|
|
1822
|
+
memoryProposalRefs,
|
|
1823
|
+
reportText: truncateEventText(review?.reportText || input.output, 4000),
|
|
1824
|
+
summary: review?.summary || `Core CM reviewed the report with ${completionDecision} completion.`,
|
|
1825
|
+
dispatchRecordId: input.dispatchRecord?.id,
|
|
1826
|
+
targetPeripheral: input.dispatchRecord?.targetPeripheral,
|
|
1827
|
+
capability: input.dispatchRecord?.capability,
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
function createCoreCmReviewMemoryProposalRefs(input) {
|
|
1831
|
+
if (input.memoryProposal?.id) {
|
|
1832
|
+
return [{
|
|
1833
|
+
memoryProposalId: input.memoryProposal.id,
|
|
1834
|
+
source: input.memoryProposal.source,
|
|
1835
|
+
summary: input.memoryProposal.summary,
|
|
1836
|
+
}];
|
|
1837
|
+
}
|
|
1838
|
+
const memoryActionProposalId = input.memoryAction?.resolvedProposalId || input.memoryAction?.proposalId;
|
|
1839
|
+
if (!memoryActionProposalId)
|
|
1840
|
+
return [];
|
|
1841
|
+
return [{
|
|
1842
|
+
memoryProposalId: memoryActionProposalId,
|
|
1843
|
+
source: "memory_action",
|
|
1844
|
+
summary: `Memory action ${input.memoryAction?.action || "unknown"} ${input.memoryAction?.status || "completed"}.`,
|
|
1845
|
+
}];
|
|
1846
|
+
}
|
|
1847
|
+
function appendTaskReviewRecord(existing, reviewRecord) {
|
|
1848
|
+
const current = Array.isArray(existing?.data?.reviewRecords)
|
|
1849
|
+
? existing.data.reviewRecords
|
|
1850
|
+
.map(normalizeCoreCmReviewRecord)
|
|
1851
|
+
.filter((item) => !!item)
|
|
1852
|
+
: [];
|
|
1853
|
+
return [...current, reviewRecord].slice(-20);
|
|
1854
|
+
}
|
|
1855
|
+
function normalizeCoreCmReviewRecord(value) {
|
|
1856
|
+
if (!value || typeof value !== "object")
|
|
1857
|
+
return null;
|
|
1858
|
+
const record = value;
|
|
1859
|
+
if (typeof record.id !== "string" || typeof record.taskId !== "string")
|
|
1860
|
+
return null;
|
|
1861
|
+
const completionDecision = normalizeCoreCmCompletionDecision(record.completionDecision) || "complete";
|
|
1862
|
+
const resultQuality = normalizeCoreCmResultQuality(record.resultQuality) || "good";
|
|
1863
|
+
return {
|
|
1864
|
+
id: record.id,
|
|
1865
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : new Date().toISOString(),
|
|
1866
|
+
taskId: record.taskId,
|
|
1867
|
+
conversationId: typeof record.conversationId === "string" ? record.conversationId : undefined,
|
|
1868
|
+
source: record.source === "core_cm_observation_review" ? "core_cm_observation_review" : "core_cm_task_report",
|
|
1869
|
+
resultQuality,
|
|
1870
|
+
completionDecision,
|
|
1871
|
+
followUpNeeded: record.followUpNeeded === true,
|
|
1872
|
+
memoryProposalRefs: normalizeCoreCmReviewMemoryRefs(record.memoryProposalRefs),
|
|
1873
|
+
reportText: typeof record.reportText === "string" ? record.reportText : "",
|
|
1874
|
+
summary: typeof record.summary === "string" ? record.summary : undefined,
|
|
1875
|
+
dispatchRecordId: typeof record.dispatchRecordId === "string" ? record.dispatchRecordId : undefined,
|
|
1876
|
+
targetPeripheral: typeof record.targetPeripheral === "string" ? record.targetPeripheral : undefined,
|
|
1877
|
+
capability: typeof record.capability === "string" ? record.capability : undefined,
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
function normalizeCoreCmReviewMemoryRefs(value) {
|
|
1881
|
+
if (!Array.isArray(value))
|
|
1882
|
+
return [];
|
|
1883
|
+
return value.flatMap((item) => {
|
|
1884
|
+
if (!item || typeof item !== "object")
|
|
1885
|
+
return [];
|
|
1886
|
+
const record = item;
|
|
1887
|
+
if (typeof record.memoryProposalId !== "string" || !record.memoryProposalId)
|
|
1888
|
+
return [];
|
|
1889
|
+
return [{
|
|
1890
|
+
memoryProposalId: record.memoryProposalId,
|
|
1891
|
+
source: typeof record.source === "string" ? record.source : undefined,
|
|
1892
|
+
summary: typeof record.summary === "string" ? record.summary : undefined,
|
|
1893
|
+
}];
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
function normalizeCoreCmResultQuality(value) {
|
|
1897
|
+
return value === "good" || value === "partial" || value === "failed" || value === "uncertain"
|
|
1898
|
+
? value
|
|
1899
|
+
: undefined;
|
|
1900
|
+
}
|
|
1901
|
+
function normalizeCoreCmCompletionDecision(value) {
|
|
1902
|
+
return value === "complete" || value === "partial" || value === "blocked" || value === "needs_follow_up"
|
|
1903
|
+
? value
|
|
1904
|
+
: undefined;
|
|
1905
|
+
}
|
|
1906
|
+
async function appendCoreCmReviewRecordEvent(input) {
|
|
1907
|
+
if (!input.conversationId)
|
|
1908
|
+
return null;
|
|
1909
|
+
try {
|
|
1910
|
+
const memoryProposalId = input.reviewRecord.memoryProposalRefs[0]?.memoryProposalId;
|
|
1911
|
+
const event = await appendCognitiveEvent({
|
|
1912
|
+
workdir: input.workdir,
|
|
1913
|
+
agentName: input.agentName,
|
|
1914
|
+
event: {
|
|
1915
|
+
stream: "cm",
|
|
1916
|
+
taskId: input.taskId,
|
|
1917
|
+
conversationId: input.conversationId,
|
|
1918
|
+
cm: "core",
|
|
1919
|
+
itemType: "review_record",
|
|
1920
|
+
status: input.reviewRecord.completionDecision === "blocked" ? "failed" : "info",
|
|
1921
|
+
summary: `Core CM reviewed the report: ${input.reviewRecord.completionDecision} (${input.reviewRecord.resultQuality}).`,
|
|
1922
|
+
memoryProposalId,
|
|
1923
|
+
data: {
|
|
1924
|
+
reviewRecord: input.reviewRecord,
|
|
1925
|
+
},
|
|
1926
|
+
refs: [{
|
|
1927
|
+
eventIds: input.sourceEventIds,
|
|
1928
|
+
conversationId: input.conversationId,
|
|
1929
|
+
messageId: input.akemonMessageId || input.ownerMessageId,
|
|
1930
|
+
taskId: input.taskId,
|
|
1931
|
+
memoryProposalId,
|
|
1932
|
+
}],
|
|
1933
|
+
},
|
|
1934
|
+
});
|
|
1935
|
+
return event.id;
|
|
1936
|
+
}
|
|
1937
|
+
catch (error) {
|
|
1938
|
+
logBestEffortError("Core CM review event append", error);
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
async function appendLocalTaskEvent(input) {
|
|
1943
|
+
try {
|
|
1944
|
+
const event = await appendCognitiveEvent({
|
|
1945
|
+
workdir: input.workdir,
|
|
1946
|
+
agentName: input.agentName,
|
|
1947
|
+
event: {
|
|
1948
|
+
stream: "task",
|
|
1949
|
+
taskId: input.taskId,
|
|
1950
|
+
conversationId: input.conversationId,
|
|
1951
|
+
phase: input.phase,
|
|
1952
|
+
stage: input.stage,
|
|
1953
|
+
status: input.status,
|
|
1954
|
+
summary: input.summary,
|
|
1955
|
+
route: input.route,
|
|
1956
|
+
refs: input.refs,
|
|
1957
|
+
data: input.data,
|
|
1958
|
+
},
|
|
1959
|
+
});
|
|
1960
|
+
try {
|
|
1961
|
+
await upsertTaskRegistryRecordFromEvent({
|
|
1962
|
+
workdir: input.workdir,
|
|
1963
|
+
agentName: input.agentName,
|
|
1964
|
+
event: event,
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
catch (error) {
|
|
1968
|
+
logBestEffortError("task registry upsert", error);
|
|
1969
|
+
}
|
|
1970
|
+
return event.id;
|
|
1971
|
+
}
|
|
1972
|
+
catch (error) {
|
|
1973
|
+
logBestEffortError("local task event append", error);
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
async function appendLocalTaskStageEvent(input) {
|
|
1978
|
+
const eventId = await appendLocalTaskEvent(input);
|
|
1979
|
+
if (eventId)
|
|
1980
|
+
input.taskEventIds.push(eventId);
|
|
1981
|
+
}
|
|
1982
|
+
async function readLocalTaskResponseRecord(input) {
|
|
1983
|
+
try {
|
|
1984
|
+
return await readTaskRegistryRecord(input);
|
|
1985
|
+
}
|
|
1986
|
+
catch (error) {
|
|
1987
|
+
logBestEffortError("task registry read", error);
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
function createTaskResponseRecord(task) {
|
|
1992
|
+
return {
|
|
1993
|
+
...task,
|
|
1994
|
+
view: createTaskSurfaceView(task),
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function createOptionalTaskResponseRecord(task) {
|
|
1998
|
+
return task ? createTaskResponseRecord(task) : null;
|
|
1999
|
+
}
|
|
2000
|
+
function createTaskViewResponse(task) {
|
|
2001
|
+
return task ? createTaskSurfaceView(task) : null;
|
|
2002
|
+
}
|
|
2003
|
+
function summarizeCoreCmTaskOutcome(input) {
|
|
2004
|
+
if (input.memoryAction) {
|
|
2005
|
+
return `Core CM completed the request through Memory CM: ${summarizeMemoryActionExecution(input.memoryAction)}`;
|
|
2006
|
+
}
|
|
2007
|
+
if (input.memoryProposal?.id) {
|
|
2008
|
+
return "Core CM completed the request and produced a memory proposal.";
|
|
2009
|
+
}
|
|
2010
|
+
if (input.peripheralEventIds.length > 0) {
|
|
2011
|
+
return "Core CM completed the request after observing a peripheral result.";
|
|
2012
|
+
}
|
|
2013
|
+
if (input.cmEventIds.length > 0) {
|
|
2014
|
+
return "Core CM completed the request with internal CM notes.";
|
|
2015
|
+
}
|
|
2016
|
+
return "Core CM completed the request with a direct answer.";
|
|
2017
|
+
}
|
|
2018
|
+
async function appendMemoryActionCmEvent(input) {
|
|
2019
|
+
if (!input.conversationId)
|
|
2020
|
+
return null;
|
|
2021
|
+
try {
|
|
2022
|
+
const event = await appendCognitiveEvent({
|
|
2023
|
+
workdir: input.workdir,
|
|
2024
|
+
agentName: input.agentName,
|
|
2025
|
+
event: {
|
|
2026
|
+
stream: "cm",
|
|
2027
|
+
taskId: input.taskId,
|
|
2028
|
+
conversationId: input.conversationId,
|
|
2029
|
+
cm: "memory",
|
|
2030
|
+
itemType: "memory_action",
|
|
2031
|
+
status: cmStatusForMemoryAction(input.memoryAction),
|
|
2032
|
+
summary: summarizeMemoryActionExecution(input.memoryAction),
|
|
2033
|
+
memoryProposalId: input.memoryAction.resolvedProposalId || input.memoryAction.proposalId,
|
|
2034
|
+
data: createMemoryActionTaskData(input.memoryAction, "memory_cm"),
|
|
2035
|
+
refs: [{
|
|
2036
|
+
eventIds: input.sourceEventIds,
|
|
2037
|
+
conversationId: input.conversationId,
|
|
2038
|
+
messageId: input.akemonMessageId || input.ownerMessageId,
|
|
2039
|
+
memoryProposalId: input.memoryAction.resolvedProposalId || input.memoryAction.proposalId,
|
|
2040
|
+
taskId: input.taskId,
|
|
2041
|
+
}],
|
|
2042
|
+
},
|
|
2043
|
+
});
|
|
2044
|
+
return event.id;
|
|
2045
|
+
}
|
|
2046
|
+
catch (error) {
|
|
2047
|
+
logBestEffortError("memory action CM event append", error);
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function cmStatusForMemoryAction(result) {
|
|
2052
|
+
if (result.status === "accepted")
|
|
2053
|
+
return "accepted";
|
|
2054
|
+
if (result.status === "rejected")
|
|
2055
|
+
return "rejected";
|
|
2056
|
+
if (result.status === "revised")
|
|
2057
|
+
return "revised";
|
|
2058
|
+
if (result.status === "not_found" || result.status === "invalid")
|
|
2059
|
+
return "failed";
|
|
2060
|
+
return "info";
|
|
2061
|
+
}
|
|
2062
|
+
function summarizeMemoryActionExecution(result) {
|
|
2063
|
+
if (result.status === "accepted")
|
|
2064
|
+
return "Memory CM accepted a pending memory proposal.";
|
|
2065
|
+
if (result.status === "rejected")
|
|
2066
|
+
return "Memory CM rejected a pending memory proposal.";
|
|
2067
|
+
if (result.status === "revised")
|
|
2068
|
+
return "Memory CM revised a pending memory proposal.";
|
|
2069
|
+
if (result.status === "listed")
|
|
2070
|
+
return "Memory CM listed pending memory proposals.";
|
|
2071
|
+
if (result.status === "shown")
|
|
2072
|
+
return "Memory CM showed current memory.";
|
|
2073
|
+
if (result.status === "not_found")
|
|
2074
|
+
return "Memory CM could not find the requested pending memory proposal.";
|
|
2075
|
+
if (result.status === "invalid")
|
|
2076
|
+
return "Memory CM could not apply the requested memory action.";
|
|
2077
|
+
return `Memory CM completed ${result.action}.`;
|
|
2078
|
+
}
|
|
2079
|
+
function createMemoryActionTaskData(result, source) {
|
|
2080
|
+
return {
|
|
2081
|
+
source,
|
|
2082
|
+
action: result.action,
|
|
2083
|
+
status: result.status,
|
|
2084
|
+
proposalId: result.proposalId,
|
|
2085
|
+
resolvedProposalId: result.resolvedProposalId,
|
|
2086
|
+
scope: result.scope,
|
|
2087
|
+
selfCount: result.selfCount,
|
|
2088
|
+
workCount: result.workCount,
|
|
2089
|
+
writtenWorkMemoryPathCount: result.writtenWorkMemoryPaths?.length || 0,
|
|
2090
|
+
outputChars: result.output.length,
|
|
2091
|
+
error: result.error,
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
async function appendOwnerDirectInteractiveSessionEvents(input) {
|
|
2095
|
+
const eventIds = { task: [], peripheral: [] };
|
|
2096
|
+
if (!input.agentName || !input.workdir)
|
|
2097
|
+
return eventIds;
|
|
2098
|
+
const taskId = createOwnerDirectTaskId("interactive_session", input.action.capability);
|
|
2099
|
+
const taskStartEventId = await appendLocalTaskEvent({
|
|
2100
|
+
agentName: input.agentName,
|
|
2101
|
+
workdir: input.workdir,
|
|
2102
|
+
taskId,
|
|
2103
|
+
phase: "delegated",
|
|
2104
|
+
stage: "dispatching",
|
|
2105
|
+
status: "running",
|
|
2106
|
+
route: "interactive-session",
|
|
2107
|
+
summary: `Owner requested ${interactiveSessionCapabilityOwnerLabel(input.action.capability)}.`,
|
|
2108
|
+
refs: [{
|
|
2109
|
+
taskId,
|
|
2110
|
+
sessionId: interactiveSessionObservationSessionId(input.observation, input.action),
|
|
2111
|
+
}],
|
|
2112
|
+
data: {
|
|
2113
|
+
source: input.source,
|
|
2114
|
+
objective: interactiveSessionCapabilityOwnerLabel(input.action.capability),
|
|
2115
|
+
capability: input.action.capability,
|
|
2116
|
+
},
|
|
2117
|
+
});
|
|
2118
|
+
if (taskStartEventId)
|
|
2119
|
+
eventIds.task.push(taskStartEventId);
|
|
2120
|
+
const peripheralEventId = await appendInteractiveSessionPeripheralEvent({
|
|
2121
|
+
agentName: input.agentName,
|
|
2122
|
+
workdir: input.workdir,
|
|
2123
|
+
taskId,
|
|
2124
|
+
action: input.action,
|
|
2125
|
+
observation: input.observation,
|
|
2126
|
+
});
|
|
2127
|
+
if (peripheralEventId)
|
|
2128
|
+
eventIds.peripheral.push(peripheralEventId);
|
|
2129
|
+
const taskEndEventId = await appendLocalTaskEvent({
|
|
2130
|
+
agentName: input.agentName,
|
|
2131
|
+
workdir: input.workdir,
|
|
2132
|
+
taskId,
|
|
2133
|
+
phase: input.observation.ok ? "completed" : "failed",
|
|
2134
|
+
stage: input.observation.ok ? "done" : "blocked",
|
|
2135
|
+
status: input.observation.ok ? "succeeded" : "failed",
|
|
2136
|
+
route: "interactive-session",
|
|
2137
|
+
summary: summarizeInteractiveSessionTaskCompletion(input.action.capability, input.observation.ok),
|
|
2138
|
+
refs: [{
|
|
2139
|
+
taskId,
|
|
2140
|
+
eventIds: [...eventIds.task, ...eventIds.peripheral],
|
|
2141
|
+
sessionId: interactiveSessionObservationSessionId(input.observation, input.action),
|
|
2142
|
+
}],
|
|
2143
|
+
data: createInteractiveSessionTaskData({
|
|
2144
|
+
source: input.source,
|
|
2145
|
+
action: input.action,
|
|
2146
|
+
observation: input.observation,
|
|
2147
|
+
}),
|
|
2148
|
+
});
|
|
2149
|
+
if (taskEndEventId)
|
|
2150
|
+
eventIds.task.push(taskEndEventId);
|
|
2151
|
+
return eventIds;
|
|
2152
|
+
}
|
|
2153
|
+
function createOwnerDirectTaskId(kind, label) {
|
|
2154
|
+
const safeLabel = label.replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 60) || "task";
|
|
2155
|
+
return createCognitiveEventId(`task_${kind}_${safeLabel}`);
|
|
2156
|
+
}
|
|
2157
|
+
function summarizeInteractiveSessionTaskCompletion(capability, ok) {
|
|
2158
|
+
const label = interactiveSessionCapabilityOwnerLabel(capability);
|
|
2159
|
+
return ok ? `${label} completed.` : `${label} failed.`;
|
|
2160
|
+
}
|
|
2161
|
+
function interactiveSessionCapabilityOwnerLabel(capability) {
|
|
2162
|
+
switch (capability) {
|
|
2163
|
+
case "start_session": return "terminal start";
|
|
2164
|
+
case "send_input": return "terminal input";
|
|
2165
|
+
case "resize_session": return "terminal resize";
|
|
2166
|
+
case "stop_session": return "terminal stop";
|
|
2167
|
+
case "inspect_session": return "terminal inspect";
|
|
2168
|
+
case "set_input_mode": return "terminal input mode change";
|
|
2169
|
+
case "list_sessions": return "terminal session list";
|
|
2170
|
+
default: return `terminal action ${capability}`;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
function ensureSoftwareAgentEnvelopeTaskId(envelope) {
|
|
2174
|
+
if (!envelope.taskId)
|
|
2175
|
+
envelope.taskId = createCognitiveEventId("task_software_agent");
|
|
2176
|
+
return envelope.taskId;
|
|
2177
|
+
}
|
|
2178
|
+
async function appendSoftwareAgentTaskStartEvent(input) {
|
|
2179
|
+
const taskId = ensureSoftwareAgentEnvelopeTaskId(input.envelope);
|
|
2180
|
+
return appendLocalTaskEvent({
|
|
2181
|
+
agentName: input.agentName,
|
|
2182
|
+
workdir: input.workdir,
|
|
2183
|
+
taskId,
|
|
2184
|
+
phase: "delegated",
|
|
2185
|
+
stage: "dispatching",
|
|
2186
|
+
status: "running",
|
|
2187
|
+
route: "software-agent",
|
|
2188
|
+
summary: `Owner delegated a software-agent task: ${truncateEventText(input.envelope.goal || input.envelope.purpose || taskId, 180)}`,
|
|
2189
|
+
refs: [{ taskId }],
|
|
2190
|
+
data: {
|
|
2191
|
+
source: input.envelope.sourceModule,
|
|
2192
|
+
softwareAgentId: input.softwareAgentId || "software-agent",
|
|
2193
|
+
purpose: input.envelope.purpose,
|
|
2194
|
+
goal: input.envelope.goal,
|
|
2195
|
+
goalChars: (input.envelope.goal || "").length,
|
|
2196
|
+
workdir: input.envelope.workdir,
|
|
2197
|
+
roleScope: input.envelope.roleScope,
|
|
2198
|
+
memoryScope: input.envelope.memoryScope,
|
|
2199
|
+
riskLevel: input.envelope.riskLevel,
|
|
2200
|
+
contextSessionId: input.envelope.contextSessionId,
|
|
2201
|
+
workMemoryDir: input.envelope.workMemoryDir,
|
|
2202
|
+
taskRecordPath: softwareAgentTaskRecordPath(softwareAgentTaskLedgerDir(input.workdir, input.agentName), taskId),
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
async function appendSoftwareAgentTaskEndEvents(input) {
|
|
2207
|
+
const eventIds = { task: [], peripheral: [] };
|
|
2208
|
+
const taskId = input.result.taskId || ensureSoftwareAgentEnvelopeTaskId(input.envelope);
|
|
2209
|
+
const taskRecordPath = softwareAgentTaskRecordPath(softwareAgentTaskLedgerDir(input.workdir, input.agentName), taskId);
|
|
2210
|
+
try {
|
|
2211
|
+
const peripheralEvent = await appendCognitiveEvent({
|
|
2212
|
+
workdir: input.workdir,
|
|
2213
|
+
agentName: input.agentName,
|
|
2214
|
+
event: {
|
|
2215
|
+
stream: "peripheral",
|
|
2216
|
+
taskId,
|
|
2217
|
+
peripheral: input.softwareAgentId || "software-agent",
|
|
2218
|
+
capability: "send_task",
|
|
2219
|
+
ok: input.result.success,
|
|
2220
|
+
summary: input.result.success
|
|
2221
|
+
? `Software-agent task ${taskId} completed.`
|
|
2222
|
+
: `Software-agent task ${taskId} failed.`,
|
|
2223
|
+
observation: {
|
|
2224
|
+
taskId,
|
|
2225
|
+
success: input.result.success,
|
|
2226
|
+
exitCode: input.result.exitCode,
|
|
2227
|
+
durationMs: input.result.durationMs,
|
|
2228
|
+
outputChars: (input.result.output || "").length,
|
|
2229
|
+
error: input.result.error,
|
|
2230
|
+
contextSessionId: input.result.contextSessionId,
|
|
2231
|
+
contextPacketPath: input.result.contextPacketPath,
|
|
2232
|
+
workMemoryDir: input.result.workMemoryDir,
|
|
2233
|
+
taskRecordPath,
|
|
2234
|
+
},
|
|
2235
|
+
refs: [{
|
|
2236
|
+
taskId,
|
|
2237
|
+
eventIds: input.sourceEventIds || [],
|
|
2238
|
+
}],
|
|
2239
|
+
},
|
|
2240
|
+
});
|
|
2241
|
+
eventIds.peripheral.push(peripheralEvent.id);
|
|
2242
|
+
}
|
|
2243
|
+
catch (error) {
|
|
2244
|
+
logBestEffortError("software-agent peripheral event append", error);
|
|
2245
|
+
}
|
|
2246
|
+
const taskEndEventId = await appendLocalTaskEvent({
|
|
2247
|
+
agentName: input.agentName,
|
|
2248
|
+
workdir: input.workdir,
|
|
2249
|
+
taskId,
|
|
2250
|
+
phase: input.result.success ? "completed" : "failed",
|
|
2251
|
+
stage: input.result.success ? "done" : "blocked",
|
|
2252
|
+
status: input.result.success ? "succeeded" : "failed",
|
|
2253
|
+
route: "software-agent",
|
|
2254
|
+
summary: input.result.success
|
|
2255
|
+
? `Software-agent task ${taskId} completed.`
|
|
2256
|
+
: `Software-agent task ${taskId} failed.`,
|
|
2257
|
+
refs: [{
|
|
2258
|
+
taskId,
|
|
2259
|
+
eventIds: [...(input.sourceEventIds || []), ...eventIds.peripheral],
|
|
2260
|
+
}],
|
|
2261
|
+
data: {
|
|
2262
|
+
softwareAgentId: input.softwareAgentId || "software-agent",
|
|
2263
|
+
success: input.result.success,
|
|
2264
|
+
exitCode: input.result.exitCode,
|
|
2265
|
+
durationMs: input.result.durationMs,
|
|
2266
|
+
outputChars: (input.result.output || "").length,
|
|
2267
|
+
error: input.result.error,
|
|
2268
|
+
contextSessionId: input.result.contextSessionId,
|
|
2269
|
+
contextPacketPath: input.result.contextPacketPath,
|
|
2270
|
+
workMemoryDir: input.result.workMemoryDir,
|
|
2271
|
+
taskRecordPath,
|
|
2272
|
+
},
|
|
2273
|
+
});
|
|
2274
|
+
if (taskEndEventId)
|
|
2275
|
+
eventIds.task.push(taskEndEventId);
|
|
2276
|
+
return eventIds;
|
|
2277
|
+
}
|
|
2278
|
+
async function appendSoftwareAgentTaskFailureEvent(input) {
|
|
2279
|
+
const taskId = ensureSoftwareAgentEnvelopeTaskId(input.envelope);
|
|
2280
|
+
return appendLocalTaskEvent({
|
|
2281
|
+
agentName: input.agentName,
|
|
2282
|
+
workdir: input.workdir,
|
|
2283
|
+
taskId,
|
|
2284
|
+
phase: "failed",
|
|
2285
|
+
stage: "blocked",
|
|
2286
|
+
status: "failed",
|
|
2287
|
+
route: "software-agent",
|
|
2288
|
+
summary: `Software-agent task ${taskId} failed before producing a result.`,
|
|
2289
|
+
refs: [{
|
|
2290
|
+
taskId,
|
|
2291
|
+
eventIds: input.sourceEventIds || [],
|
|
2292
|
+
}],
|
|
2293
|
+
data: {
|
|
2294
|
+
softwareAgentId: input.softwareAgentId || "software-agent",
|
|
2295
|
+
error: input.error,
|
|
2296
|
+
taskRecordPath: softwareAgentTaskRecordPath(softwareAgentTaskLedgerDir(input.workdir, input.agentName), taskId),
|
|
2297
|
+
},
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
function truncateEventText(value, maxChars) {
|
|
2301
|
+
const text = value.trim();
|
|
2302
|
+
if (text.length <= maxChars)
|
|
2303
|
+
return text;
|
|
2304
|
+
return `${text.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
2305
|
+
}
|
|
2306
|
+
async function appendInteractiveSessionOutcomeEvent(input) {
|
|
2307
|
+
const { snapshot } = input.event;
|
|
2308
|
+
const taskId = `task_interactive_session_${snapshot.sessionId}`;
|
|
2309
|
+
await appendLocalTaskEvent({
|
|
2310
|
+
agentName: input.agentName,
|
|
2311
|
+
workdir: input.workdir,
|
|
2312
|
+
taskId,
|
|
2313
|
+
phase: snapshot.status === "failed" ? "failed" : "completed",
|
|
2314
|
+
stage: snapshot.status === "failed" ? "blocked" : "done",
|
|
2315
|
+
status: snapshot.status === "failed" ? "failed" : "succeeded",
|
|
2316
|
+
route: "interactive-session",
|
|
2317
|
+
summary: `Interactive session ${snapshot.sessionId} ended with status ${snapshot.status}.`,
|
|
2318
|
+
refs: [{
|
|
2319
|
+
taskId,
|
|
2320
|
+
sessionId: snapshot.sessionId,
|
|
2321
|
+
}],
|
|
2322
|
+
data: {
|
|
2323
|
+
source: "interactive_session_exit",
|
|
2324
|
+
sessionId: snapshot.sessionId,
|
|
2325
|
+
tool: snapshot.tool,
|
|
2326
|
+
status: snapshot.status,
|
|
2327
|
+
command: snapshot.commandLineDisplay,
|
|
2328
|
+
workdir: snapshot.workdir,
|
|
2329
|
+
logPath: snapshot.logPath,
|
|
2330
|
+
outputChars: snapshot.outputChars,
|
|
2331
|
+
exitCode: input.event.exitCode,
|
|
2332
|
+
signal: input.event.signal,
|
|
2333
|
+
startedAt: snapshot.startedAt,
|
|
2334
|
+
endedAt: snapshot.endedAt,
|
|
2335
|
+
},
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
function schedulePassiveReflection(input) {
|
|
2339
|
+
setTimeout(() => {
|
|
2340
|
+
runPassiveReflectionOnce(input).catch((error) => {
|
|
2341
|
+
logBestEffortError("passive reflection", error);
|
|
2342
|
+
});
|
|
2343
|
+
}, 0);
|
|
2344
|
+
}
|
|
2345
|
+
async function appendComputeAuditEvent(input) {
|
|
2346
|
+
const summary = {
|
|
2347
|
+
origin: input.req.origin,
|
|
2348
|
+
taskKind: input.req.taskKind,
|
|
2349
|
+
purpose: input.req.purpose,
|
|
2350
|
+
priority: input.req.priority,
|
|
2351
|
+
routeId: input.invocation?.routeId,
|
|
2352
|
+
resourceId: input.invocation?.resourceId,
|
|
2353
|
+
resourceKind: input.invocation?.resourceKind,
|
|
2354
|
+
provider: input.invocation?.provider,
|
|
2355
|
+
protocol: input.invocation?.protocol,
|
|
2356
|
+
engine: input.invocation?.engine,
|
|
2357
|
+
model: input.invocation?.model,
|
|
2358
|
+
reasoningEffort: input.invocation?.reasoningEffort,
|
|
2359
|
+
verbosity: input.invocation?.verbosity,
|
|
2360
|
+
command: input.invocation?.command,
|
|
2361
|
+
inputChars: input.promptChars,
|
|
2362
|
+
outputChars: input.outputChars,
|
|
2363
|
+
durationMs: input.durationMs,
|
|
2364
|
+
success: input.success,
|
|
2365
|
+
...(input.error ? { error: input.error } : {}),
|
|
2366
|
+
};
|
|
2367
|
+
try {
|
|
2368
|
+
const event = await appendCognitiveEvent({
|
|
2369
|
+
workdir: input.workdir,
|
|
2370
|
+
agentName: input.agentName,
|
|
2371
|
+
event: {
|
|
2372
|
+
stream: "compute",
|
|
2373
|
+
taskId: input.req.taskId,
|
|
2374
|
+
...summary,
|
|
2375
|
+
refs: input.req.refs || [],
|
|
2376
|
+
},
|
|
2377
|
+
});
|
|
2378
|
+
return { ...summary, eventId: event.id };
|
|
2379
|
+
}
|
|
2380
|
+
catch (error) {
|
|
2381
|
+
logBestEffortError("compute audit event append", error);
|
|
2382
|
+
return summary;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
async function appendInteractiveSessionPeripheralEvent(input) {
|
|
2386
|
+
try {
|
|
2387
|
+
const sessionId = interactiveSessionObservationSessionId(input.observation, input.action);
|
|
2388
|
+
const event = await appendCognitiveEvent({
|
|
2389
|
+
workdir: input.workdir,
|
|
2390
|
+
agentName: input.agentName,
|
|
2391
|
+
event: {
|
|
2392
|
+
stream: "peripheral",
|
|
2393
|
+
taskId: input.taskId,
|
|
2394
|
+
conversationId: input.conversationId,
|
|
2395
|
+
peripheral: input.observation.peripheral,
|
|
2396
|
+
capability: input.observation.capability,
|
|
2397
|
+
ok: input.observation.ok,
|
|
2398
|
+
summary: input.observation.summary,
|
|
2399
|
+
sessionId,
|
|
2400
|
+
actionReason: input.action.reason,
|
|
2401
|
+
observation: {
|
|
2402
|
+
capability: input.observation.capability,
|
|
2403
|
+
ok: input.observation.ok,
|
|
2404
|
+
summary: input.observation.summary,
|
|
2405
|
+
data: input.observation.data,
|
|
2406
|
+
error: input.observation.error,
|
|
2407
|
+
},
|
|
2408
|
+
refs: [
|
|
2409
|
+
{
|
|
2410
|
+
conversationId: input.conversationId,
|
|
2411
|
+
messageId: input.ownerMessageId,
|
|
2412
|
+
sessionId,
|
|
2413
|
+
taskId: input.taskId,
|
|
2414
|
+
},
|
|
2415
|
+
],
|
|
2416
|
+
},
|
|
2417
|
+
});
|
|
2418
|
+
return event.id;
|
|
2419
|
+
}
|
|
2420
|
+
catch (error) {
|
|
2421
|
+
logBestEffortError("interactive session peripheral event append", error);
|
|
2422
|
+
return null;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
async function appendCoreCmResultEvents(input) {
|
|
2426
|
+
const eventIds = [];
|
|
2427
|
+
if (!input.conversationId)
|
|
2428
|
+
return eventIds;
|
|
2429
|
+
try {
|
|
2430
|
+
const processNotes = (input.processNotes || [])
|
|
2431
|
+
.map((note) => (typeof note === "string" ? note.trim() : ""))
|
|
2432
|
+
.filter(Boolean);
|
|
2433
|
+
for (let index = 0; index < processNotes.length; index += 1) {
|
|
2434
|
+
const event = await appendCognitiveEvent({
|
|
2435
|
+
workdir: input.workdir,
|
|
2436
|
+
agentName: input.agentName,
|
|
2437
|
+
event: {
|
|
2438
|
+
stream: "cm",
|
|
2439
|
+
taskId: input.taskId,
|
|
2440
|
+
conversationId: input.conversationId,
|
|
2441
|
+
cm: "core",
|
|
2442
|
+
itemType: "process_note",
|
|
2443
|
+
status: "info",
|
|
2444
|
+
summary: processNotes[index],
|
|
2445
|
+
data: {
|
|
2446
|
+
order: index + 1,
|
|
2447
|
+
},
|
|
2448
|
+
refs: [
|
|
2449
|
+
{
|
|
2450
|
+
eventIds: input.sourceEventIds,
|
|
2451
|
+
conversationId: input.conversationId,
|
|
2452
|
+
messageId: input.akemonMessageId || input.ownerMessageId,
|
|
2453
|
+
taskId: input.taskId,
|
|
2454
|
+
},
|
|
2455
|
+
],
|
|
2456
|
+
},
|
|
2457
|
+
});
|
|
2458
|
+
eventIds.push(event.id);
|
|
2459
|
+
}
|
|
2460
|
+
if (input.memoryAction) {
|
|
2461
|
+
const memoryActionEventId = await appendMemoryActionCmEvent({
|
|
2462
|
+
agentName: input.agentName,
|
|
2463
|
+
workdir: input.workdir,
|
|
2464
|
+
conversationId: input.conversationId,
|
|
2465
|
+
ownerMessageId: input.ownerMessageId,
|
|
2466
|
+
akemonMessageId: input.akemonMessageId,
|
|
2467
|
+
sourceEventIds: input.sourceEventIds,
|
|
2468
|
+
taskId: input.taskId,
|
|
2469
|
+
memoryAction: input.memoryAction,
|
|
2470
|
+
});
|
|
2471
|
+
if (memoryActionEventId)
|
|
2472
|
+
eventIds.push(memoryActionEventId);
|
|
2473
|
+
}
|
|
2474
|
+
if (!input.memoryProposal)
|
|
2475
|
+
return eventIds;
|
|
2476
|
+
const proposal = input.memoryProposal;
|
|
2477
|
+
const event = await appendCognitiveEvent({
|
|
2478
|
+
workdir: input.workdir,
|
|
2479
|
+
agentName: input.agentName,
|
|
2480
|
+
event: {
|
|
2481
|
+
stream: "cm",
|
|
2482
|
+
taskId: input.taskId,
|
|
2483
|
+
conversationId: input.conversationId,
|
|
2484
|
+
cm: "memory",
|
|
2485
|
+
itemType: "memory_proposal",
|
|
2486
|
+
status: "pending",
|
|
2487
|
+
summary: proposal.summary || "Akemon Memory CM created a pending memory proposal.",
|
|
2488
|
+
memoryProposalId: proposal.id,
|
|
2489
|
+
data: {
|
|
2490
|
+
source: proposal.source,
|
|
2491
|
+
selfCount: Array.isArray(proposal.self) ? proposal.self.length : 0,
|
|
2492
|
+
workCount: Array.isArray(proposal.work) ? proposal.work.length : 0,
|
|
2493
|
+
},
|
|
2494
|
+
refs: [
|
|
2495
|
+
{
|
|
2496
|
+
eventIds: input.sourceEventIds,
|
|
2497
|
+
conversationId: input.conversationId,
|
|
2498
|
+
messageId: input.akemonMessageId || input.ownerMessageId,
|
|
2499
|
+
memoryProposalId: proposal.id,
|
|
2500
|
+
taskId: input.taskId,
|
|
2501
|
+
},
|
|
2502
|
+
],
|
|
2503
|
+
},
|
|
2504
|
+
});
|
|
2505
|
+
eventIds.push(event.id);
|
|
2506
|
+
}
|
|
2507
|
+
catch (error) {
|
|
2508
|
+
logBestEffortError("Core CM result event append", error);
|
|
2509
|
+
}
|
|
2510
|
+
return eventIds;
|
|
2511
|
+
}
|
|
2512
|
+
async function executeInteractiveSessionViaPeripheral(workbench, peripheral, action) {
|
|
2513
|
+
return peripheral ? peripheral.execute(action) : executeInteractiveSessionAction(workbench, action);
|
|
2514
|
+
}
|
|
2515
|
+
function interactiveSessionObservationSessionId(observation, action) {
|
|
2516
|
+
const observedSession = observation.data?.session;
|
|
2517
|
+
if (observedSession && typeof observedSession === "object") {
|
|
2518
|
+
const sessionId = observedSession.sessionId;
|
|
2519
|
+
if (typeof sessionId === "string" && sessionId)
|
|
2520
|
+
return sessionId;
|
|
2521
|
+
}
|
|
2522
|
+
const args = action?.args;
|
|
2523
|
+
return typeof args?.sessionId === "string" && args.sessionId ? args.sessionId : undefined;
|
|
2524
|
+
}
|
|
2525
|
+
function createInvalidInteractiveSessionObservation(message) {
|
|
2526
|
+
return {
|
|
2527
|
+
kind: "peripheral_observation",
|
|
2528
|
+
peripheral: "interactive-session",
|
|
2529
|
+
capability: "list_sessions",
|
|
2530
|
+
ok: false,
|
|
2531
|
+
summary: message,
|
|
2532
|
+
error: message,
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
function createInteractiveSessionTaskData(input) {
|
|
2536
|
+
return {
|
|
2537
|
+
source: input.source,
|
|
2538
|
+
capability: input.observation.capability,
|
|
2539
|
+
ok: input.observation.ok,
|
|
2540
|
+
sessionId: interactiveSessionObservationSessionId(input.observation, input.action),
|
|
2541
|
+
outputChars: input.outputChars,
|
|
2542
|
+
error: input.observation.error,
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
async function executeInteractiveSessionActionProposal(workbench, peripheral, proposal) {
|
|
2546
|
+
const action = {
|
|
2547
|
+
capability: proposal.capability,
|
|
2548
|
+
args: proposal.args,
|
|
2549
|
+
reason: proposal.reason,
|
|
2550
|
+
};
|
|
2551
|
+
if (peripheral)
|
|
2552
|
+
return peripheral.execute(action);
|
|
2553
|
+
if (!workbench) {
|
|
2554
|
+
return {
|
|
2555
|
+
kind: "peripheral_observation",
|
|
2556
|
+
peripheral: "interactive-session",
|
|
2557
|
+
capability: proposal.capability,
|
|
2558
|
+
ok: false,
|
|
2559
|
+
reason: proposal.reason,
|
|
2560
|
+
summary: "I could not use interactive sessions because the adapter is not available in this runtime.",
|
|
2561
|
+
error: "Interactive session adapter is not available in this runtime.",
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
return executeInteractiveSessionAction(workbench, action);
|
|
2565
|
+
}
|
|
2566
|
+
function parseMemoryChatCommand(input) {
|
|
2567
|
+
const trimmed = input.text.trim();
|
|
2568
|
+
if (!trimmed.startsWith("/memory"))
|
|
2569
|
+
return null;
|
|
2570
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice("/memory".length).trim());
|
|
2571
|
+
const action = tokens[0] || "pending";
|
|
2572
|
+
if (["pending", "list", "list_pending"].includes(action)) {
|
|
2573
|
+
return {
|
|
2574
|
+
proposal: {
|
|
2575
|
+
kind: "memory_action",
|
|
2576
|
+
action: "list_pending",
|
|
2577
|
+
proposalId: tokens[1] || "latest",
|
|
2578
|
+
scope: "all",
|
|
2579
|
+
},
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
if (["show", "current", "status", "what"].includes(action)) {
|
|
2583
|
+
return {
|
|
2584
|
+
proposal: {
|
|
2585
|
+
kind: "memory_action",
|
|
2586
|
+
action: "show_memory",
|
|
2587
|
+
scope: parseMemoryCommandScope(tokens[1]),
|
|
2588
|
+
},
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
if (["revise", "update", "edit"].includes(action)) {
|
|
2592
|
+
let proposalId = tokens[1] || "latest";
|
|
2593
|
+
let rest = tokens.slice(2);
|
|
2594
|
+
if (isMemoryCommandScope(proposalId)) {
|
|
2595
|
+
rest = tokens.slice(1);
|
|
2596
|
+
proposalId = "latest";
|
|
2597
|
+
}
|
|
2598
|
+
const scope = isMemoryCommandScope(rest[0]) ? parseMemoryCommandScope(rest.shift()) : "work";
|
|
2599
|
+
const revisedText = rest.join(" ").trim();
|
|
2600
|
+
if (!revisedText) {
|
|
2601
|
+
return { output: "Usage: /memory revise [proposalId|latest] [self|work] <replacement memory text>" };
|
|
2602
|
+
}
|
|
2603
|
+
return {
|
|
2604
|
+
proposal: {
|
|
2605
|
+
kind: "memory_action",
|
|
2606
|
+
action: "revise_pending",
|
|
2607
|
+
proposalId,
|
|
2608
|
+
scope,
|
|
2609
|
+
memory: scope === "self" ? { self: [revisedText] } : { work: [revisedText] },
|
|
2610
|
+
},
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
if (["accept", "remember", "accept_pending"].includes(action)) {
|
|
2614
|
+
return {
|
|
2615
|
+
proposal: {
|
|
2616
|
+
kind: "memory_action",
|
|
2617
|
+
action: "accept_pending",
|
|
2618
|
+
proposalId: tokens[1] || "latest",
|
|
2619
|
+
scope: parseMemoryCommandScope(tokens[2]),
|
|
2620
|
+
},
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
if (["reject", "ignore", "reject_pending"].includes(action)) {
|
|
2624
|
+
return {
|
|
2625
|
+
proposal: {
|
|
2626
|
+
kind: "memory_action",
|
|
2627
|
+
action: "reject_pending",
|
|
2628
|
+
proposalId: tokens[1] || "latest",
|
|
2629
|
+
scope: "all",
|
|
2630
|
+
},
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
return { output: "Usage: /memory pending | /memory show [all|self|work] | /memory revise [proposalId|latest] [self|work] <text> | /memory accept [proposalId|latest] [all|self|work] | /memory reject [proposalId|latest]" };
|
|
2634
|
+
}
|
|
2635
|
+
function parseMemoryCommandScope(value) {
|
|
2636
|
+
return value === "self" || value === "work" || value === "all" ? value : "all";
|
|
2637
|
+
}
|
|
2638
|
+
function isMemoryCommandScope(value) {
|
|
2639
|
+
return value === "self" || value === "work" || value === "all";
|
|
2640
|
+
}
|
|
2641
|
+
function executeReflectionChatCommand(input) {
|
|
2642
|
+
const trimmed = input.text.trim();
|
|
2643
|
+
const command = trimmed.match(/^\/([a-zA-Z][a-zA-Z0-9_-]*)/)?.[1] || "";
|
|
2644
|
+
if (!["reflection", "reflections"].includes(command))
|
|
2645
|
+
return null;
|
|
2646
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice(command.length + 1).trim());
|
|
2647
|
+
const action = tokens[0] || "show";
|
|
2648
|
+
if (!["show", "list", "latest", "recent"].includes(action)) {
|
|
2649
|
+
return Promise.resolve("Usage: /reflection show [count]");
|
|
2650
|
+
}
|
|
2651
|
+
const countToken = action === "latest" ? "1" : tokens[1];
|
|
2652
|
+
const count = readReflectionCount(countToken);
|
|
2653
|
+
return formatRecentReflectionRecords(input.workdir, input.agentName, count);
|
|
2654
|
+
}
|
|
2655
|
+
async function formatRecentReflectionRecords(workdir, agentName, count) {
|
|
2656
|
+
const records = await readCognitiveArtifacts({
|
|
2657
|
+
workdir,
|
|
2658
|
+
agentName,
|
|
2659
|
+
kind: "reflection",
|
|
2660
|
+
limit: count,
|
|
2661
|
+
});
|
|
2662
|
+
if (!records.length) {
|
|
2663
|
+
return "Reflection CM 目前还没有留下反思记录。它只会在主聊天产生外设观察或 CM 事件后,异步记录高价值发现。";
|
|
2664
|
+
}
|
|
2665
|
+
const newestFirst = [...records].reverse();
|
|
2666
|
+
const lines = newestFirst.flatMap((record, index) => {
|
|
2667
|
+
const score = typeof record.valueScore === "number" ? `,价值 ${Math.round(record.valueScore * 100)}%` : "";
|
|
2668
|
+
const header = `${index + 1}. ${formatShortTimestamp(record.createdAt)},Reflection CM 发现:${record.insight}`;
|
|
2669
|
+
const details = [
|
|
2670
|
+
`原因:${record.reasoning}`,
|
|
2671
|
+
...(record.suggestedAction ? [`建议:${record.suggestedAction}`] : []),
|
|
2672
|
+
`信心:${formatConfidence(record.confidence)}${score}`,
|
|
2673
|
+
];
|
|
2674
|
+
return [header, ...details, ""];
|
|
2675
|
+
});
|
|
2676
|
+
return [`最近 ${records.length} 条 Reflection CM 记录:`, "", ...lines].join("\n").trim();
|
|
2677
|
+
}
|
|
2678
|
+
function readReflectionCount(token) {
|
|
2679
|
+
const count = Number(token || 5);
|
|
2680
|
+
if (!Number.isFinite(count) || count <= 0)
|
|
2681
|
+
return 5;
|
|
2682
|
+
return Math.max(1, Math.min(20, Math.floor(count)));
|
|
2683
|
+
}
|
|
2684
|
+
function formatShortTimestamp(value) {
|
|
2685
|
+
const date = new Date(value);
|
|
2686
|
+
if (Number.isNaN(date.getTime()))
|
|
2687
|
+
return value;
|
|
2688
|
+
return date.toLocaleString("zh-CN", {
|
|
2689
|
+
year: "numeric",
|
|
2690
|
+
month: "2-digit",
|
|
2691
|
+
day: "2-digit",
|
|
2692
|
+
hour: "2-digit",
|
|
2693
|
+
minute: "2-digit",
|
|
2694
|
+
hour12: false,
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
function formatConfidence(value) {
|
|
2698
|
+
if (value === "high")
|
|
2699
|
+
return "高";
|
|
2700
|
+
if (value === "low")
|
|
2701
|
+
return "低";
|
|
2702
|
+
return "中";
|
|
2703
|
+
}
|
|
2704
|
+
function parseWorkbenchChatCommandAction(text) {
|
|
2705
|
+
const trimmed = text.trim();
|
|
2706
|
+
if (!trimmed.startsWith("/"))
|
|
2707
|
+
return null;
|
|
2708
|
+
const command = trimmed.match(/^\/([a-zA-Z][a-zA-Z0-9_-]*)/)?.[1] || "";
|
|
2709
|
+
if (!["start", "sessions", "input", "mode", "input-mode", "stop", "resize"].includes(command))
|
|
2710
|
+
return null;
|
|
2711
|
+
try {
|
|
2712
|
+
switch (command) {
|
|
2713
|
+
case "sessions":
|
|
2714
|
+
return {
|
|
2715
|
+
action: {
|
|
2716
|
+
capability: "list_sessions",
|
|
2717
|
+
reason: "owner used /sessions in Akemon chat",
|
|
2718
|
+
},
|
|
2719
|
+
};
|
|
2720
|
+
case "start": {
|
|
2721
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice("/start".length).trim());
|
|
2722
|
+
if (!tokens.length) {
|
|
2723
|
+
throw new Error("Usage: /start <codex|claude|cursor|shell|custom> [args...]");
|
|
2724
|
+
}
|
|
2725
|
+
const tool = normalizeWorkbenchTool(tokens[0]);
|
|
2726
|
+
const request = tool === "custom"
|
|
2727
|
+
? {
|
|
2728
|
+
tool,
|
|
2729
|
+
command: tokens[1],
|
|
2730
|
+
args: tokens.slice(2),
|
|
2731
|
+
}
|
|
2732
|
+
: {
|
|
2733
|
+
tool,
|
|
2734
|
+
args: tokens.slice(1),
|
|
2735
|
+
};
|
|
2736
|
+
return {
|
|
2737
|
+
action: {
|
|
2738
|
+
capability: "start_session",
|
|
2739
|
+
args: request,
|
|
2740
|
+
reason: "owner used /start in Akemon chat",
|
|
2741
|
+
},
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
case "input": {
|
|
2745
|
+
const match = trimmed.match(/^\/input\s+(\S+)\s+([\s\S]+)$/);
|
|
2746
|
+
if (!match)
|
|
2747
|
+
throw new Error("Usage: /input <sessionId> <text>");
|
|
2748
|
+
return {
|
|
2749
|
+
action: {
|
|
2750
|
+
capability: "send_input",
|
|
2751
|
+
args: {
|
|
2752
|
+
sessionId: match[1],
|
|
2753
|
+
input: match[2],
|
|
2754
|
+
},
|
|
2755
|
+
reason: "owner used /input in Akemon chat",
|
|
2756
|
+
},
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
case "mode":
|
|
2760
|
+
case "input-mode": {
|
|
2761
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice(command === "mode" ? "/mode".length : "/input-mode".length).trim());
|
|
2762
|
+
if (tokens.length !== 2)
|
|
2763
|
+
throw new Error(`Usage: /${command} <sessionId|active|latest> <line|tui>`);
|
|
2764
|
+
return {
|
|
2765
|
+
action: {
|
|
2766
|
+
capability: "set_input_mode",
|
|
2767
|
+
args: {
|
|
2768
|
+
sessionId: tokens[0],
|
|
2769
|
+
inputMode: normalizeWorkbenchInputMode(tokens[1]),
|
|
2770
|
+
},
|
|
2771
|
+
reason: `owner used /${command} in Akemon chat`,
|
|
2772
|
+
},
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
case "stop": {
|
|
2776
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice("/stop".length).trim());
|
|
2777
|
+
if (tokens.length !== 1)
|
|
2778
|
+
throw new Error("Usage: /stop <sessionId>");
|
|
2779
|
+
return {
|
|
2780
|
+
action: {
|
|
2781
|
+
capability: "stop_session",
|
|
2782
|
+
args: { sessionId: tokens[0] },
|
|
2783
|
+
reason: "owner used /stop in Akemon chat",
|
|
2784
|
+
},
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
case "resize": {
|
|
2788
|
+
const tokens = splitWorkbenchCommandLine(trimmed.slice("/resize".length).trim());
|
|
2789
|
+
if (tokens.length !== 3)
|
|
2790
|
+
throw new Error("Usage: /resize <sessionId> <cols> <rows>");
|
|
2791
|
+
const cols = Number(tokens[1]);
|
|
2792
|
+
const rows = Number(tokens[2]);
|
|
2793
|
+
if (!Number.isInteger(cols) || cols <= 0 || !Number.isInteger(rows) || rows <= 0) {
|
|
2794
|
+
throw new Error("Usage: /resize <sessionId> <cols> <rows>");
|
|
2795
|
+
}
|
|
2796
|
+
return {
|
|
2797
|
+
action: {
|
|
2798
|
+
capability: "resize_session",
|
|
2799
|
+
args: {
|
|
2800
|
+
sessionId: tokens[0],
|
|
2801
|
+
cols,
|
|
2802
|
+
rows,
|
|
2803
|
+
},
|
|
2804
|
+
reason: "owner used /resize in Akemon chat",
|
|
2805
|
+
},
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
default:
|
|
2809
|
+
return null;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
catch (error) {
|
|
2813
|
+
return { error: `I could not complete this Workbench action: ${error?.message || String(error)}` };
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
function splitWorkbenchCommandLine(input) {
|
|
2817
|
+
const tokens = [];
|
|
2818
|
+
let current = "";
|
|
2819
|
+
let quote = null;
|
|
2820
|
+
let escaping = false;
|
|
2821
|
+
for (const char of input) {
|
|
2822
|
+
if (escaping) {
|
|
2823
|
+
current += char;
|
|
2824
|
+
escaping = false;
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
if (char === "\\") {
|
|
2828
|
+
escaping = true;
|
|
2829
|
+
continue;
|
|
2830
|
+
}
|
|
2831
|
+
if (quote) {
|
|
2832
|
+
if (char === quote)
|
|
2833
|
+
quote = null;
|
|
2834
|
+
else
|
|
2835
|
+
current += char;
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (char === "\"" || char === "'") {
|
|
2839
|
+
quote = char;
|
|
2840
|
+
continue;
|
|
2841
|
+
}
|
|
2842
|
+
if (/\s/.test(char)) {
|
|
2843
|
+
if (current) {
|
|
2844
|
+
tokens.push(current);
|
|
2845
|
+
current = "";
|
|
2846
|
+
}
|
|
2847
|
+
continue;
|
|
2848
|
+
}
|
|
2849
|
+
current += char;
|
|
2850
|
+
}
|
|
2851
|
+
if (escaping)
|
|
2852
|
+
throw new Error("Invalid Workbench command: trailing escape");
|
|
2853
|
+
if (quote)
|
|
2854
|
+
throw new Error("Invalid Workbench command: unterminated quote");
|
|
2855
|
+
if (current)
|
|
2856
|
+
tokens.push(current);
|
|
2857
|
+
return tokens;
|
|
2858
|
+
}
|
|
2859
|
+
export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
2860
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
2861
|
+
return;
|
|
2862
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
2863
|
+
if (!softwareAgent)
|
|
2864
|
+
return;
|
|
2865
|
+
const envelope = await readOwnerSoftwareAgentEnvelope(req, res, deps);
|
|
2866
|
+
if (!envelope)
|
|
2867
|
+
return;
|
|
2868
|
+
ensureSoftwareAgentEnvelopeTaskId(envelope);
|
|
2869
|
+
const taskEventIds = [];
|
|
2870
|
+
const taskStartEventId = await appendSoftwareAgentTaskStartEvent({
|
|
2871
|
+
agentName: deps.agentName,
|
|
2872
|
+
workdir: deps.workdir,
|
|
2873
|
+
envelope,
|
|
2874
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
2875
|
+
});
|
|
2876
|
+
if (taskStartEventId)
|
|
2877
|
+
taskEventIds.push(taskStartEventId);
|
|
2878
|
+
const abortController = new AbortController();
|
|
2879
|
+
let responseFinished = false;
|
|
2880
|
+
let streamStarted = false;
|
|
2881
|
+
res.on("close", () => {
|
|
2882
|
+
if (!responseFinished)
|
|
2883
|
+
abortController.abort();
|
|
2884
|
+
});
|
|
2885
|
+
const ensureStreamStarted = () => {
|
|
2886
|
+
if (streamStarted)
|
|
2887
|
+
return;
|
|
2888
|
+
streamStarted = true;
|
|
2889
|
+
res.writeHead(200, {
|
|
2890
|
+
"Content-Type": "application/x-ndjson; charset=utf-8",
|
|
2891
|
+
"Cache-Control": "no-cache",
|
|
2892
|
+
"X-Accel-Buffering": "no",
|
|
2893
|
+
});
|
|
2894
|
+
res.flushHeaders?.();
|
|
2895
|
+
};
|
|
2896
|
+
try {
|
|
2897
|
+
const result = await softwareAgent.sendTask(envelope, {
|
|
2898
|
+
signal: abortController.signal,
|
|
2899
|
+
observer: {
|
|
2900
|
+
onStart(event) {
|
|
2901
|
+
ensureStreamStarted();
|
|
2902
|
+
writeSoftwareAgentStreamEvent(res, {
|
|
2903
|
+
type: "start",
|
|
2904
|
+
taskId: event.taskId,
|
|
2905
|
+
commandLine: event.commandLine,
|
|
2906
|
+
contextSessionId: event.contextSessionId,
|
|
2907
|
+
contextPacketPath: event.contextPacketPath,
|
|
2908
|
+
workMemoryDir: event.workMemoryDir,
|
|
2909
|
+
});
|
|
2910
|
+
},
|
|
2911
|
+
onStream(event) {
|
|
2912
|
+
ensureStreamStarted();
|
|
2913
|
+
writeSoftwareAgentStreamEvent(res, {
|
|
2914
|
+
type: event.stream,
|
|
2915
|
+
taskId: event.taskId,
|
|
2916
|
+
chunk: event.chunk,
|
|
2917
|
+
});
|
|
2918
|
+
},
|
|
2919
|
+
onEnd(event) {
|
|
2920
|
+
ensureStreamStarted();
|
|
2921
|
+
writeSoftwareAgentStreamEvent(res, {
|
|
2922
|
+
type: "end",
|
|
2923
|
+
taskId: event.taskId,
|
|
2924
|
+
exitCode: event.exitCode,
|
|
2925
|
+
durationMs: event.durationMs,
|
|
2926
|
+
result: event.result,
|
|
2927
|
+
contextSessionId: event.contextSessionId,
|
|
2928
|
+
contextPacketPath: event.contextPacketPath,
|
|
2929
|
+
workMemoryDir: event.workMemoryDir,
|
|
2930
|
+
});
|
|
2931
|
+
},
|
|
2932
|
+
},
|
|
2933
|
+
});
|
|
2934
|
+
await appendSoftwareAgentTaskEndEvents({
|
|
2935
|
+
agentName: deps.agentName,
|
|
2936
|
+
workdir: deps.workdir,
|
|
2937
|
+
envelope,
|
|
2938
|
+
result,
|
|
2939
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
2940
|
+
sourceEventIds: taskEventIds,
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
catch (err) {
|
|
2944
|
+
await appendSoftwareAgentTaskFailureEvent({
|
|
2945
|
+
agentName: deps.agentName,
|
|
2946
|
+
workdir: deps.workdir,
|
|
2947
|
+
envelope,
|
|
2948
|
+
softwareAgentId: deps.softwareAgent?.id,
|
|
2949
|
+
sourceEventIds: taskEventIds,
|
|
2950
|
+
error: err.message || String(err),
|
|
2951
|
+
});
|
|
2952
|
+
if (!streamStarted) {
|
|
2953
|
+
const busy = String(err.message || "").includes("busy");
|
|
2954
|
+
writeJsonResponse(res, busy ? 409 : 500, { error: err.message || String(err) });
|
|
2955
|
+
responseFinished = true;
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
writeSoftwareAgentStreamEvent(res, {
|
|
2959
|
+
type: "error",
|
|
2960
|
+
error: err.message || String(err),
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
finally {
|
|
2964
|
+
responseFinished = true;
|
|
2965
|
+
if (streamStarted && !res.writableEnded)
|
|
2966
|
+
res.end();
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
export async function handleSoftwareAgentStatusHttp(req, res, deps) {
|
|
2970
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
2971
|
+
return;
|
|
2972
|
+
const softwareAgent = requireSoftwareAgent(res, deps.softwareAgent);
|
|
2973
|
+
if (!softwareAgent)
|
|
2974
|
+
return;
|
|
2975
|
+
writeJsonResponse(res, 200, softwareAgent.getState(), true);
|
|
2976
|
+
}
|
|
2977
|
+
export async function handleSoftwareAgentTasksHttp(req, res, deps) {
|
|
2978
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
2979
|
+
return;
|
|
2980
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
2981
|
+
const basePath = "/self/software-agent/tasks";
|
|
2982
|
+
const taskLedgerDir = softwareAgentTaskLedgerDir(deps.workdir, deps.agentName);
|
|
2983
|
+
if (url.pathname === basePath) {
|
|
2984
|
+
const limit = readPositiveIntQuery(url.searchParams.get("limit"), 20, 100);
|
|
2985
|
+
const tasks = listSoftwareAgentTaskRecords(taskLedgerDir, limit, {
|
|
2986
|
+
contextSessionId: url.searchParams.get("session") || undefined,
|
|
2987
|
+
});
|
|
2988
|
+
writeJsonResponse(res, 200, { tasks }, true);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
if (url.pathname.startsWith(`${basePath}/`)) {
|
|
2992
|
+
const taskId = decodeURIComponent(url.pathname.slice(basePath.length + 1));
|
|
2993
|
+
if (!taskId || taskId.includes("/")) {
|
|
2994
|
+
writeJsonResponse(res, 400, { error: "Invalid software-agent task id" });
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
const task = readSoftwareAgentTaskRecord(taskLedgerDir, taskId);
|
|
2998
|
+
if (!task) {
|
|
2999
|
+
writeJsonResponse(res, 404, { error: "Software-agent task not found" });
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
let contextSession;
|
|
3003
|
+
if (readBooleanQuery(url.searchParams.get("includeContext")) && task.contextSession?.sessionId) {
|
|
3004
|
+
try {
|
|
3005
|
+
contextSession = readSoftwareAgentContextSession(softwareAgentContextSessionDir(deps.workdir, deps.agentName), task.contextSession.sessionId, { includeContextPacket: true });
|
|
3006
|
+
}
|
|
3007
|
+
catch {
|
|
3008
|
+
contextSession = null;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
writeJsonResponse(res, 200, { task, ...(contextSession ? { contextSession } : {}) }, true);
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
writeJsonResponse(res, 404, { error: "Software-agent task endpoint not found" });
|
|
3015
|
+
}
|
|
3016
|
+
export async function handleLocalTasksHttp(req, res, deps) {
|
|
3017
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
3018
|
+
return;
|
|
3019
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
3020
|
+
const basePath = "/self/tasks";
|
|
3021
|
+
if (url.pathname === basePath) {
|
|
3022
|
+
const status = readTaskStatusQuery(url.searchParams.get("status"));
|
|
3023
|
+
const route = url.searchParams.get("route") || undefined;
|
|
3024
|
+
const visibility = readTaskVisibilityQuery(url.searchParams.get("visibility"));
|
|
3025
|
+
const parentTaskId = url.searchParams.get("parentTaskId") || undefined;
|
|
3026
|
+
const tasks = await listTaskRegistryRecords({
|
|
3027
|
+
workdir: deps.workdir,
|
|
3028
|
+
agentName: deps.agentName,
|
|
3029
|
+
limit: readPositiveIntQuery(url.searchParams.get("limit"), 50, 200),
|
|
3030
|
+
status,
|
|
3031
|
+
route,
|
|
3032
|
+
visibility,
|
|
3033
|
+
parentTaskId,
|
|
3034
|
+
});
|
|
3035
|
+
const taskViews = tasks.map(createTaskSurfaceView);
|
|
3036
|
+
writeJsonResponse(res, 200, {
|
|
3037
|
+
tasks: tasks.map(createTaskResponseRecord),
|
|
3038
|
+
taskViews,
|
|
3039
|
+
}, true);
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
if (url.pathname.startsWith(`${basePath}/`)) {
|
|
3043
|
+
const taskId = decodeURIComponent(url.pathname.slice(basePath.length + 1));
|
|
3044
|
+
if (!taskId || taskId.includes("/")) {
|
|
3045
|
+
writeJsonResponse(res, 400, { error: "Invalid task id" });
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
const task = await readTaskRegistryRecord({
|
|
3049
|
+
workdir: deps.workdir,
|
|
3050
|
+
agentName: deps.agentName,
|
|
3051
|
+
taskId,
|
|
3052
|
+
});
|
|
3053
|
+
if (!task) {
|
|
3054
|
+
writeJsonResponse(res, 404, { error: "Task not found" });
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
writeJsonResponse(res, 200, {
|
|
3058
|
+
task: createTaskResponseRecord(task),
|
|
3059
|
+
taskView: createTaskSurfaceView(task),
|
|
3060
|
+
}, true);
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
writeJsonResponse(res, 404, { error: "Task endpoint not found" });
|
|
3064
|
+
}
|
|
3065
|
+
export async function handleSoftwareAgentContextSessionsHttp(req, res, deps) {
|
|
3066
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
3067
|
+
return;
|
|
3068
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
3069
|
+
const basePath = "/self/software-agent/sessions";
|
|
3070
|
+
const contextSessionDir = softwareAgentContextSessionDir(deps.workdir, deps.agentName);
|
|
3071
|
+
if (url.pathname === basePath) {
|
|
3072
|
+
const limit = readPositiveIntQuery(url.searchParams.get("limit"), 20, 100);
|
|
3073
|
+
const sessions = listSoftwareAgentContextSessions(contextSessionDir, limit);
|
|
3074
|
+
writeJsonResponse(res, 200, { sessions }, true);
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
if (url.pathname.startsWith(`${basePath}/`)) {
|
|
3078
|
+
const sessionId = decodeURIComponent(url.pathname.slice(basePath.length + 1));
|
|
3079
|
+
if (!sessionId || sessionId.includes("/")) {
|
|
3080
|
+
writeJsonResponse(res, 400, { error: "Invalid software-agent context session id" });
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
let session;
|
|
3084
|
+
try {
|
|
3085
|
+
session = readSoftwareAgentContextSession(contextSessionDir, sessionId, {
|
|
3086
|
+
includeContextPacket: readBooleanQuery(url.searchParams.get("includeContext")),
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
catch (err) {
|
|
3090
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid software-agent context session id" });
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
if (!session) {
|
|
3094
|
+
writeJsonResponse(res, 404, { error: "Software-agent context session not found" });
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
writeJsonResponse(res, 200, { session }, true);
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
writeJsonResponse(res, 404, { error: "Software-agent context session endpoint not found" });
|
|
3101
|
+
}
|
|
3102
|
+
function softwareAgentTaskLedgerDir(workdir, agentName) {
|
|
3103
|
+
return join(workdir, ".akemon", "agents", agentName, "software-agent", "tasks");
|
|
3104
|
+
}
|
|
3105
|
+
function softwareAgentContextSessionDir(workdir, agentName) {
|
|
3106
|
+
return join(workdir, ".akemon", "agents", agentName, "software-agent", "sessions");
|
|
310
3107
|
}
|
|
311
3108
|
function readPositiveIntQuery(value, fallback, max) {
|
|
312
3109
|
if (!value)
|
|
@@ -316,11 +3113,51 @@ function readPositiveIntQuery(value, fallback, max) {
|
|
|
316
3113
|
return fallback;
|
|
317
3114
|
return Math.min(parsed, max);
|
|
318
3115
|
}
|
|
3116
|
+
function readBooleanQuery(value) {
|
|
3117
|
+
return value === "1" || value === "true" || value === "yes";
|
|
3118
|
+
}
|
|
3119
|
+
function readTaskStatusQuery(value) {
|
|
3120
|
+
if (value === "pending"
|
|
3121
|
+
|| value === "running"
|
|
3122
|
+
|| value === "waiting"
|
|
3123
|
+
|| value === "succeeded"
|
|
3124
|
+
|| value === "failed"
|
|
3125
|
+
|| value === "info") {
|
|
3126
|
+
return value;
|
|
3127
|
+
}
|
|
3128
|
+
return undefined;
|
|
3129
|
+
}
|
|
3130
|
+
function readTaskVisibilityQuery(value) {
|
|
3131
|
+
if (value === "primary" || value === "technical")
|
|
3132
|
+
return value;
|
|
3133
|
+
return undefined;
|
|
3134
|
+
}
|
|
3135
|
+
function readOptionalBooleanBody(value, field) {
|
|
3136
|
+
if (value === undefined || value === null)
|
|
3137
|
+
return false;
|
|
3138
|
+
if (typeof value !== "boolean")
|
|
3139
|
+
throw new Error(`Invalid ${field}: expected boolean`);
|
|
3140
|
+
return value;
|
|
3141
|
+
}
|
|
3142
|
+
function readOptionalPositiveIntBody(value, field) {
|
|
3143
|
+
if (value === undefined || value === null)
|
|
3144
|
+
return undefined;
|
|
3145
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
3146
|
+
throw new Error(`Invalid ${field}: expected positive integer`);
|
|
3147
|
+
}
|
|
3148
|
+
return value;
|
|
3149
|
+
}
|
|
319
3150
|
function writeSoftwareAgentStreamEvent(res, event) {
|
|
320
3151
|
if (res.destroyed)
|
|
321
3152
|
return;
|
|
322
3153
|
res.write(`${JSON.stringify(redactSecrets(event))}\n`);
|
|
323
3154
|
}
|
|
3155
|
+
function writeWorkbenchSseEvent(res, eventName, data) {
|
|
3156
|
+
if (res.destroyed)
|
|
3157
|
+
return;
|
|
3158
|
+
res.write(`event: ${eventName}\n`);
|
|
3159
|
+
res.write(`data: ${JSON.stringify(redactSecrets(data))}\n\n`);
|
|
3160
|
+
}
|
|
324
3161
|
export async function handleSoftwareAgentResetHttp(req, res, deps) {
|
|
325
3162
|
if (!requireOwnerRequest(req, res, deps.options))
|
|
326
3163
|
return;
|
|
@@ -328,43 +3165,403 @@ export async function handleSoftwareAgentResetHttp(req, res, deps) {
|
|
|
328
3165
|
if (!softwareAgent)
|
|
329
3166
|
return;
|
|
330
3167
|
try {
|
|
331
|
-
await softwareAgent.resetSession();
|
|
332
|
-
writeJsonResponse(res, 200, { ok: true, state: softwareAgent.getState() }, true);
|
|
3168
|
+
await softwareAgent.resetSession();
|
|
3169
|
+
writeJsonResponse(res, 200, { ok: true, state: softwareAgent.getState() }, true);
|
|
3170
|
+
}
|
|
3171
|
+
catch (err) {
|
|
3172
|
+
writeJsonResponse(res, 500, { error: err.message || String(err) });
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
export async function handleWorkbenchSessionsHttp(req, res, deps) {
|
|
3176
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
3177
|
+
return;
|
|
3178
|
+
const workbench = requireWorkbench(res, deps.workbench);
|
|
3179
|
+
if (!workbench)
|
|
3180
|
+
return;
|
|
3181
|
+
await workbench.ready();
|
|
3182
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
3183
|
+
const basePath = "/self/workbench/sessions";
|
|
3184
|
+
const suffix = url.pathname === basePath ? "" : url.pathname.slice(basePath.length + 1);
|
|
3185
|
+
const [sessionId, action] = suffix ? suffix.split("/") : ["", ""];
|
|
3186
|
+
try {
|
|
3187
|
+
if (url.pathname === basePath && req.method === "GET") {
|
|
3188
|
+
writeJsonResponse(res, 200, {
|
|
3189
|
+
sessions: workbench.listSessions(),
|
|
3190
|
+
activeSessionId: workbench.getActiveSessionId(),
|
|
3191
|
+
...(deps.agentName ? { agentName: deps.agentName } : {}),
|
|
3192
|
+
}, true);
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
if (url.pathname === basePath && req.method === "POST") {
|
|
3196
|
+
const body = await readJsonBody(req);
|
|
3197
|
+
const action = {
|
|
3198
|
+
capability: "start_session",
|
|
3199
|
+
args: normalizeWorkbenchStartRequest(body),
|
|
3200
|
+
reason: "owner used Workbench HTTP start",
|
|
3201
|
+
};
|
|
3202
|
+
const observation = await executeInteractiveSessionViaPeripheral(workbench, deps.interactiveSessionPeripheral, action);
|
|
3203
|
+
await appendOwnerDirectInteractiveSessionEvents({
|
|
3204
|
+
agentName: deps.agentName,
|
|
3205
|
+
workdir: deps.workdir,
|
|
3206
|
+
source: "workbench_http",
|
|
3207
|
+
action,
|
|
3208
|
+
observation,
|
|
3209
|
+
});
|
|
3210
|
+
if (!observation.ok)
|
|
3211
|
+
throw new Error(observation.error || observation.summary);
|
|
3212
|
+
const sessionId = interactiveSessionObservationSessionId(observation, action);
|
|
3213
|
+
const session = sessionId ? workbench.getSession(sessionId) : null;
|
|
3214
|
+
writeJsonResponse(res, 201, { session }, true);
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes("\\")) {
|
|
3218
|
+
writeJsonResponse(res, 404, { error: "Workbench session endpoint not found" });
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
if (!action && req.method === "GET") {
|
|
3222
|
+
const session = workbench.getSession(sessionId);
|
|
3223
|
+
if (!session) {
|
|
3224
|
+
writeJsonResponse(res, 404, { error: "Workbench session not found" });
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
writeJsonResponse(res, 200, { session }, true);
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
if (action === "activate" && req.method === "POST") {
|
|
3231
|
+
const session = workbench.setActiveSession(sessionId);
|
|
3232
|
+
writeJsonResponse(res, 200, { ok: true, activeSessionId: session.sessionId, session }, true);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
if (action === "tail" && req.method === "GET") {
|
|
3236
|
+
const session = workbench.getSession(sessionId);
|
|
3237
|
+
if (!session) {
|
|
3238
|
+
writeJsonResponse(res, 404, { error: "Workbench session not found" });
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
writeJsonResponse(res, 200, { sessionId, tail: session.tail }, true);
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
if (action === "stream" && req.method === "GET") {
|
|
3245
|
+
const session = workbench.getSession(sessionId);
|
|
3246
|
+
if (!session) {
|
|
3247
|
+
writeJsonResponse(res, 404, { error: "Workbench session not found" });
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
res.writeHead(200, {
|
|
3251
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
3252
|
+
"Cache-Control": "no-cache",
|
|
3253
|
+
"Connection": "keep-alive",
|
|
3254
|
+
"X-Accel-Buffering": "no",
|
|
3255
|
+
});
|
|
3256
|
+
res.flushHeaders?.();
|
|
3257
|
+
writeWorkbenchSseEvent(res, "snapshot", { session });
|
|
3258
|
+
let done = false;
|
|
3259
|
+
let unsubscribe = () => { };
|
|
3260
|
+
const cleanup = () => {
|
|
3261
|
+
if (done)
|
|
3262
|
+
return;
|
|
3263
|
+
done = true;
|
|
3264
|
+
unsubscribe();
|
|
3265
|
+
};
|
|
3266
|
+
unsubscribe = workbench.subscribe((event) => {
|
|
3267
|
+
if (event.sessionId !== sessionId || done)
|
|
3268
|
+
return;
|
|
3269
|
+
if (event.type === "output") {
|
|
3270
|
+
writeWorkbenchSseEvent(res, "output", event);
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
if (event.type === "status") {
|
|
3274
|
+
writeWorkbenchSseEvent(res, "status", event);
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
writeWorkbenchSseEvent(res, "exit", event);
|
|
3278
|
+
cleanup();
|
|
3279
|
+
if (!res.writableEnded)
|
|
3280
|
+
res.end();
|
|
3281
|
+
});
|
|
3282
|
+
res.on("close", cleanup);
|
|
3283
|
+
const latestSession = workbench.getSession(sessionId) || session;
|
|
3284
|
+
if (latestSession.status !== "running") {
|
|
3285
|
+
writeWorkbenchSseEvent(res, "exit", {
|
|
3286
|
+
type: "exit",
|
|
3287
|
+
sessionId,
|
|
3288
|
+
exitCode: latestSession.exitCode,
|
|
3289
|
+
signal: latestSession.signal,
|
|
3290
|
+
snapshot: latestSession,
|
|
3291
|
+
});
|
|
3292
|
+
cleanup();
|
|
3293
|
+
if (!res.writableEnded)
|
|
3294
|
+
res.end();
|
|
3295
|
+
}
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (action === "input" && req.method === "POST") {
|
|
3299
|
+
const body = await readJsonBody(req);
|
|
3300
|
+
const session = workbench.writeInput(sessionId, normalizeWorkbenchInputRequest(body));
|
|
3301
|
+
writeJsonResponse(res, 200, { ok: true, session }, true);
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
if (action === "input-mode" && req.method === "POST") {
|
|
3305
|
+
const body = await readJsonBody(req);
|
|
3306
|
+
const interactiveAction = {
|
|
3307
|
+
capability: "set_input_mode",
|
|
3308
|
+
args: {
|
|
3309
|
+
sessionId,
|
|
3310
|
+
inputMode: normalizeWorkbenchInputMode(body?.inputMode),
|
|
3311
|
+
},
|
|
3312
|
+
reason: "owner used Workbench HTTP input-mode",
|
|
3313
|
+
};
|
|
3314
|
+
const observation = await executeInteractiveSessionViaPeripheral(workbench, deps.interactiveSessionPeripheral, interactiveAction);
|
|
3315
|
+
await appendOwnerDirectInteractiveSessionEvents({
|
|
3316
|
+
agentName: deps.agentName,
|
|
3317
|
+
workdir: deps.workdir,
|
|
3318
|
+
source: "workbench_http",
|
|
3319
|
+
action: interactiveAction,
|
|
3320
|
+
observation,
|
|
3321
|
+
});
|
|
3322
|
+
if (!observation.ok)
|
|
3323
|
+
throw new Error(observation.error || observation.summary);
|
|
3324
|
+
const session = workbench.getSession(sessionId);
|
|
3325
|
+
writeJsonResponse(res, 200, { ok: true, session }, true);
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
if (action === "resize" && req.method === "POST") {
|
|
3329
|
+
const body = await readJsonBody(req);
|
|
3330
|
+
const resize = normalizeWorkbenchResizeRequest(body);
|
|
3331
|
+
const interactiveAction = {
|
|
3332
|
+
capability: "resize_session",
|
|
3333
|
+
args: {
|
|
3334
|
+
sessionId,
|
|
3335
|
+
cols: resize.cols,
|
|
3336
|
+
rows: resize.rows,
|
|
3337
|
+
},
|
|
3338
|
+
reason: "owner used Workbench HTTP resize",
|
|
3339
|
+
};
|
|
3340
|
+
const observation = await executeInteractiveSessionViaPeripheral(workbench, deps.interactiveSessionPeripheral, interactiveAction);
|
|
3341
|
+
await appendOwnerDirectInteractiveSessionEvents({
|
|
3342
|
+
agentName: deps.agentName,
|
|
3343
|
+
workdir: deps.workdir,
|
|
3344
|
+
source: "workbench_http",
|
|
3345
|
+
action: interactiveAction,
|
|
3346
|
+
observation,
|
|
3347
|
+
});
|
|
3348
|
+
if (!observation.ok)
|
|
3349
|
+
throw new Error(observation.error || observation.summary);
|
|
3350
|
+
const session = workbench.getSession(sessionId);
|
|
3351
|
+
writeJsonResponse(res, 200, { ok: true, session }, true);
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
if (action === "stop" && req.method === "POST") {
|
|
3355
|
+
const interactiveAction = {
|
|
3356
|
+
capability: "stop_session",
|
|
3357
|
+
args: { sessionId },
|
|
3358
|
+
reason: "owner used Workbench HTTP stop",
|
|
3359
|
+
};
|
|
3360
|
+
const observation = await executeInteractiveSessionViaPeripheral(workbench, deps.interactiveSessionPeripheral, interactiveAction);
|
|
3361
|
+
await appendOwnerDirectInteractiveSessionEvents({
|
|
3362
|
+
agentName: deps.agentName,
|
|
3363
|
+
workdir: deps.workdir,
|
|
3364
|
+
source: "workbench_http",
|
|
3365
|
+
action: interactiveAction,
|
|
3366
|
+
observation,
|
|
3367
|
+
});
|
|
3368
|
+
if (!observation.ok)
|
|
3369
|
+
throw new Error(observation.error || observation.summary);
|
|
3370
|
+
const session = workbench.getSession(sessionId);
|
|
3371
|
+
writeJsonResponse(res, 200, { ok: true, session }, true);
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
writeJsonResponse(res, 404, { error: "Workbench session endpoint not found" });
|
|
333
3375
|
}
|
|
334
3376
|
catch (err) {
|
|
335
|
-
|
|
3377
|
+
const message = err.message || String(err);
|
|
3378
|
+
const status = /not found/i.test(message) ? 404
|
|
3379
|
+
: /limit reached|not running/i.test(message) ? 409
|
|
3380
|
+
: 400;
|
|
3381
|
+
writeJsonResponse(res, status, { error: message });
|
|
336
3382
|
}
|
|
337
3383
|
}
|
|
338
|
-
import { RelayPeripheral } from "./relay-peripheral.js";
|
|
339
|
-
import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
|
|
340
|
-
import { EngineQueue } from "./engine-queue.js";
|
|
341
|
-
import { BioStateModule } from "./bio-module.js";
|
|
342
|
-
import { MemoryModule } from "./memory-module.js";
|
|
343
|
-
import { RoleModule } from "./role-module.js";
|
|
344
|
-
import { TaskModule } from "./task-module.js";
|
|
345
|
-
import { SocialModule } from "./social-module.js";
|
|
346
|
-
import { LongTermModule } from "./longterm-module.js";
|
|
347
|
-
import { ReflectionModule } from "./reflection-module.js";
|
|
348
|
-
import { ScriptModule } from "./script-module.js";
|
|
349
|
-
import { FileEventLog, PersistentEventBus } from "./event-bus.js";
|
|
350
|
-
import { CodexSoftwareAgentPeripheral, createOwnerTaskEnvelope, listSoftwareAgentTaskRecords, readSoftwareAgentTaskRecord, } from "./software-agent-peripheral.js";
|
|
351
|
-
import { buildSoftwareAgentMemorySummary } from "./software-agent-memory.js";
|
|
352
|
-
import { SIG, sig } from "./types.js";
|
|
353
|
-
import { loadConversation, listConversations, buildLLMContext } from "./context.js";
|
|
354
|
-
import { redactSecrets } from "./redaction.js";
|
|
355
|
-
import { createMcpServer, initMcpProxy, createMcpProxyServer } from "./mcp-server.js";
|
|
356
|
-
import { autoRoute, runCollaborativeQuery } from "./agent-utils.js";
|
|
357
3384
|
// createMcpServer, initMcpProxy, createMcpProxyServer → see mcp-server.ts
|
|
358
3385
|
const LLM_ENGINES = LLM_ENGINES_SET;
|
|
3386
|
+
async function handleOwnerActionHttpRoute(req, res, deps) {
|
|
3387
|
+
const requestPath = req.url?.split("?")[0] || "";
|
|
3388
|
+
if (requestPath === "/self/workbench/sessions"
|
|
3389
|
+
|| requestPath.startsWith("/self/workbench/sessions/")) {
|
|
3390
|
+
await handleWorkbenchSessionsHttp(req, res, {
|
|
3391
|
+
options: deps.options,
|
|
3392
|
+
workbench: deps.workbench,
|
|
3393
|
+
interactiveSessionPeripheral: deps.interactiveSessionPeripheral,
|
|
3394
|
+
agentName: deps.agentName,
|
|
3395
|
+
workdir: deps.workdir,
|
|
3396
|
+
});
|
|
3397
|
+
return true;
|
|
3398
|
+
}
|
|
3399
|
+
if (req.url === "/self/software-agent/run-stream" && req.method === "POST") {
|
|
3400
|
+
await handleSoftwareAgentRunStreamHttp(req, res, {
|
|
3401
|
+
options: deps.options,
|
|
3402
|
+
workdir: deps.workdir,
|
|
3403
|
+
agentName: deps.agentName,
|
|
3404
|
+
softwareAgent: deps.softwareAgent,
|
|
3405
|
+
});
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3408
|
+
if (req.url === "/self/software-agent/run" && req.method === "POST") {
|
|
3409
|
+
await handleSoftwareAgentRunHttp(req, res, {
|
|
3410
|
+
options: deps.options,
|
|
3411
|
+
workdir: deps.workdir,
|
|
3412
|
+
agentName: deps.agentName,
|
|
3413
|
+
softwareAgent: deps.softwareAgent,
|
|
3414
|
+
});
|
|
3415
|
+
return true;
|
|
3416
|
+
}
|
|
3417
|
+
if (req.url === "/self/software-agent/status" && req.method === "GET") {
|
|
3418
|
+
await handleSoftwareAgentStatusHttp(req, res, {
|
|
3419
|
+
options: deps.options,
|
|
3420
|
+
softwareAgent: deps.softwareAgent,
|
|
3421
|
+
});
|
|
3422
|
+
return true;
|
|
3423
|
+
}
|
|
3424
|
+
if (req.method === "GET"
|
|
3425
|
+
&& (requestPath === "/self/tasks" || requestPath.startsWith("/self/tasks/"))) {
|
|
3426
|
+
await handleLocalTasksHttp(req, res, {
|
|
3427
|
+
options: deps.options,
|
|
3428
|
+
workdir: deps.workdir,
|
|
3429
|
+
agentName: deps.agentName,
|
|
3430
|
+
});
|
|
3431
|
+
return true;
|
|
3432
|
+
}
|
|
3433
|
+
if (req.method === "GET"
|
|
3434
|
+
&& (requestPath === "/self/software-agent/sessions" || requestPath.startsWith("/self/software-agent/sessions/"))) {
|
|
3435
|
+
await handleSoftwareAgentContextSessionsHttp(req, res, {
|
|
3436
|
+
options: deps.options,
|
|
3437
|
+
workdir: deps.workdir,
|
|
3438
|
+
agentName: deps.agentName,
|
|
3439
|
+
});
|
|
3440
|
+
return true;
|
|
3441
|
+
}
|
|
3442
|
+
if (req.method === "GET"
|
|
3443
|
+
&& (requestPath === "/self/software-agent/tasks" || requestPath.startsWith("/self/software-agent/tasks/"))) {
|
|
3444
|
+
await handleSoftwareAgentTasksHttp(req, res, {
|
|
3445
|
+
options: deps.options,
|
|
3446
|
+
workdir: deps.workdir,
|
|
3447
|
+
agentName: deps.agentName,
|
|
3448
|
+
});
|
|
3449
|
+
return true;
|
|
3450
|
+
}
|
|
3451
|
+
if (req.url === "/self/software-agent/reset" && req.method === "POST") {
|
|
3452
|
+
await handleSoftwareAgentResetHttp(req, res, {
|
|
3453
|
+
options: deps.options,
|
|
3454
|
+
softwareAgent: deps.softwareAgent,
|
|
3455
|
+
});
|
|
3456
|
+
return true;
|
|
3457
|
+
}
|
|
3458
|
+
if (req.url === "/self/message" && req.method === "POST") {
|
|
3459
|
+
await handleLocalAkemonMessageHttp(req, res, {
|
|
3460
|
+
options: deps.options,
|
|
3461
|
+
agentName: deps.agentName,
|
|
3462
|
+
workdir: deps.workdir,
|
|
3463
|
+
localPort: deps.localPort,
|
|
3464
|
+
workbench: deps.workbench,
|
|
3465
|
+
interactiveSessionPeripheral: deps.interactiveSessionPeripheral,
|
|
3466
|
+
requestCompute: deps.requestCompute,
|
|
3467
|
+
passiveReflection: true,
|
|
3468
|
+
});
|
|
3469
|
+
return true;
|
|
3470
|
+
}
|
|
3471
|
+
return false;
|
|
3472
|
+
}
|
|
3473
|
+
async function handleSelfHttpRoute(req, res, deps) {
|
|
3474
|
+
const requestPath = req.url?.split("?")[0] || "";
|
|
3475
|
+
if (req.url === "/self/state" && req.method === "GET") {
|
|
3476
|
+
const state = await getSelfState(deps.workdir, deps.agentName);
|
|
3477
|
+
if (deps.relay?.connected) {
|
|
3478
|
+
try {
|
|
3479
|
+
const agents = await deps.relay.listAgents({ online: true, public: true });
|
|
3480
|
+
const self = agents.find((a) => a.name === deps.agentName);
|
|
3481
|
+
state.credits = self?.credits ?? 0;
|
|
3482
|
+
state.level = self?.level ?? 0;
|
|
3483
|
+
}
|
|
3484
|
+
catch {
|
|
3485
|
+
state.credits = null;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(state, null, 2));
|
|
3489
|
+
return true;
|
|
3490
|
+
}
|
|
3491
|
+
if (requestPath === "/self/peripherals/explore" && req.method === "GET") {
|
|
3492
|
+
const url = new URL(req.url || requestPath, `http://localhost`);
|
|
3493
|
+
const id = url.searchParams.get("id") || undefined;
|
|
3494
|
+
const configured = await loadPeripheralRecords(deps.agentName);
|
|
3495
|
+
const records = mergePeripheralRecords(configured, deps.runtimePeripheralRecords);
|
|
3496
|
+
const briefing = await buildPeripheralExploreBriefing({
|
|
3497
|
+
records,
|
|
3498
|
+
runtimePeripherals: deps.peripherals,
|
|
3499
|
+
id,
|
|
3500
|
+
});
|
|
3501
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(briefing, null, 2));
|
|
3502
|
+
return true;
|
|
3503
|
+
}
|
|
3504
|
+
if (requestPath === "/self/peripherals" && req.method === "GET") {
|
|
3505
|
+
const configured = await loadPeripheralRecords(deps.agentName);
|
|
3506
|
+
const records = mergePeripheralRecords(configured, deps.runtimePeripheralRecords);
|
|
3507
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ records }, null, 2));
|
|
3508
|
+
return true;
|
|
3509
|
+
}
|
|
3510
|
+
if (req.url === "/self/revive" && req.method === "POST") {
|
|
3511
|
+
await reviveAgent(deps.workdir, deps.agentName);
|
|
3512
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ ok: true, message: "Agent revived. Energy=50, Hunger=50." }));
|
|
3513
|
+
return true;
|
|
3514
|
+
}
|
|
3515
|
+
if (req.url?.startsWith("/self/task-history") && req.method === "GET") {
|
|
3516
|
+
const url = new URL(req.url, `http://localhost`);
|
|
3517
|
+
const limit = parseInt(url.searchParams.get("limit") || "50") || 50;
|
|
3518
|
+
const history = await loadTaskHistory(deps.workdir, deps.agentName, limit);
|
|
3519
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(history, null, 2));
|
|
3520
|
+
return true;
|
|
3521
|
+
}
|
|
3522
|
+
if (req.url === "/self/directives" && req.method === "GET") {
|
|
3523
|
+
const dirs = await loadDirectives(deps.workdir, deps.agentName);
|
|
3524
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(dirs, null, 2));
|
|
3525
|
+
return true;
|
|
3526
|
+
}
|
|
3527
|
+
if (req.url === "/self/canvas" && req.method === "GET") {
|
|
3528
|
+
const entries = await loadRecentCanvasEntries(deps.workdir, deps.agentName, 10);
|
|
3529
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(entries, null, 2));
|
|
3530
|
+
return true;
|
|
3531
|
+
}
|
|
3532
|
+
if (req.url === "/self/conversations" && req.method === "GET") {
|
|
3533
|
+
const list = await listConversations(deps.workdir, deps.agentName);
|
|
3534
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(list, null, 2));
|
|
3535
|
+
return true;
|
|
3536
|
+
}
|
|
3537
|
+
if (req.url?.startsWith("/self/conversation/") && req.method === "GET") {
|
|
3538
|
+
const convId = decodeURIComponent(req.url.slice("/self/conversation/".length));
|
|
3539
|
+
if (!convId) {
|
|
3540
|
+
res.writeHead(400).end("Missing conversation ID");
|
|
3541
|
+
return true;
|
|
3542
|
+
}
|
|
3543
|
+
const conv = await loadConversation(deps.workdir, deps.agentName, convId);
|
|
3544
|
+
const config = await loadAgentConfig(deps.workdir, deps.agentName);
|
|
3545
|
+
const budget = config.context_budget ?? 4096;
|
|
3546
|
+
const { recentStartIndex } = buildLLMContext(conv, budget);
|
|
3547
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({
|
|
3548
|
+
summary: conv.summary,
|
|
3549
|
+
rounds: conv.rounds,
|
|
3550
|
+
recentStartIndex,
|
|
3551
|
+
}, null, 2));
|
|
3552
|
+
return true;
|
|
3553
|
+
}
|
|
3554
|
+
return false;
|
|
3555
|
+
}
|
|
359
3556
|
// ---------------------------------------------------------------------------
|
|
360
3557
|
// Engine execution — delegates to EnginePeripheral (V2 Step 3)
|
|
361
3558
|
// ---------------------------------------------------------------------------
|
|
362
3559
|
/** Unified engine runner — delegates to EnginePeripheral */
|
|
363
|
-
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay, signal, origin, routing, taskId, routeRequest) {
|
|
3560
|
+
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay, signal, origin, routing, taskId, routeRequest, engineResources) {
|
|
364
3561
|
if (!_engineP) {
|
|
365
3562
|
throw new Error("Engine peripheral not initialized");
|
|
366
3563
|
}
|
|
367
|
-
const result = _engineP.runEngine(task, allowAll, extraAllowedTools, signal, origin, routing, taskId, routeRequest);
|
|
3564
|
+
const result = _engineP.runEngine(task, allowAll, extraAllowedTools, signal, origin, routing, taskId, routeRequest, engineResources);
|
|
368
3565
|
// Sync trace back to module-level for reporting
|
|
369
3566
|
result.then(() => { lastEngineTrace = _engineP.lastTrace; }).catch(() => { lastEngineTrace = _engineP.lastTrace; });
|
|
370
3567
|
return result;
|
|
@@ -373,14 +3570,18 @@ function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, re
|
|
|
373
3570
|
// pullFromRelay → see relay-peripheral.ts
|
|
374
3571
|
export async function serve(options) {
|
|
375
3572
|
const workdir = options.workdir || process.cwd();
|
|
3573
|
+
let registryHeartbeat = null;
|
|
376
3574
|
// Reclaim stale CLI children from any previous daemon crash before starting.
|
|
377
3575
|
await scanAndKillOrphans();
|
|
378
|
-
// V2: Relay peripheral — unified relay API access
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
3576
|
+
// V2: Relay peripheral — unified relay API access. Local-only mode does not
|
|
3577
|
+
// register this peripheral, so modules cannot accidentally use relay APIs.
|
|
3578
|
+
const relay = options.relayHttp
|
|
3579
|
+
? new RelayPeripheral({
|
|
3580
|
+
httpUrl: options.relayHttp,
|
|
3581
|
+
secretKey: options.secretKey || "",
|
|
3582
|
+
agentName: options.agentName,
|
|
3583
|
+
})
|
|
3584
|
+
: null;
|
|
384
3585
|
// V2: Engine peripheral — unified engine execution
|
|
385
3586
|
const engineP = new EnginePeripheral({
|
|
386
3587
|
engine: options.engine || "claude",
|
|
@@ -392,23 +3593,54 @@ export async function serve(options) {
|
|
|
392
3593
|
rawMaxRounds: 20,
|
|
393
3594
|
relay: options.relayHttp ? { http: options.relayHttp, agentName: options.agentName } : undefined,
|
|
394
3595
|
});
|
|
3596
|
+
options.relay = relay ?? undefined;
|
|
3597
|
+
options.enginePeripheral = engineP;
|
|
3598
|
+
_engineP = engineP;
|
|
395
3599
|
// Expose port to engine subprocesses so they can callback to local MCP server
|
|
396
3600
|
process.env.AKEMON_PORT = String(options.port);
|
|
397
3601
|
if (options.key)
|
|
398
3602
|
process.env.AKEMON_KEY = options.key;
|
|
399
|
-
// Initialize MCP proxy
|
|
3603
|
+
// Initialize MCP proxy lazily so HTTP startup is not gated by child MCP boot.
|
|
400
3604
|
let mcpProxy = null;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
3605
|
+
let mcpProxyPromise = null;
|
|
3606
|
+
const ensureMcpProxy = () => {
|
|
3607
|
+
if (!options.mcpServer)
|
|
3608
|
+
return Promise.resolve(null);
|
|
3609
|
+
if (mcpProxy)
|
|
3610
|
+
return Promise.resolve(mcpProxy);
|
|
3611
|
+
if (!mcpProxyPromise) {
|
|
3612
|
+
mcpProxyPromise = initMcpProxy(options.mcpServer, workdir)
|
|
3613
|
+
.then((proxy) => {
|
|
3614
|
+
mcpProxy = proxy;
|
|
3615
|
+
return proxy;
|
|
3616
|
+
})
|
|
3617
|
+
.catch((err) => {
|
|
3618
|
+
console.error(`[mcp-proxy] Failed to start child MCP server: ${err.message || String(err)}`);
|
|
3619
|
+
process.exit(1);
|
|
3620
|
+
});
|
|
408
3621
|
}
|
|
409
|
-
|
|
3622
|
+
return mcpProxyPromise;
|
|
3623
|
+
};
|
|
410
3624
|
const sessions = new Map();
|
|
411
3625
|
const publisherIds = new Map();
|
|
3626
|
+
const interactiveSessions = new InteractiveSessionManager({
|
|
3627
|
+
agentName: options.agentName,
|
|
3628
|
+
baseWorkdir: workdir,
|
|
3629
|
+
});
|
|
3630
|
+
const workbench = interactiveSessions;
|
|
3631
|
+
const interactiveSessionPeripheral = new InteractiveSessionPeripheral(interactiveSessions);
|
|
3632
|
+
workbench.ready()
|
|
3633
|
+
.then(() => console.log("[workbench] session recovery ready"))
|
|
3634
|
+
.catch((error) => logBestEffortError("workbench session recovery", error));
|
|
3635
|
+
workbench.subscribe((event) => {
|
|
3636
|
+
if (event.type !== "exit")
|
|
3637
|
+
return;
|
|
3638
|
+
appendInteractiveSessionOutcomeEvent({
|
|
3639
|
+
agentName: options.agentName,
|
|
3640
|
+
workdir,
|
|
3641
|
+
event,
|
|
3642
|
+
}).catch((error) => logBestEffortError("interactive session outcome event append", error));
|
|
3643
|
+
});
|
|
412
3644
|
// Build deps for MCP server
|
|
413
3645
|
const mcpDeps = {
|
|
414
3646
|
runEngine,
|
|
@@ -426,52 +3658,37 @@ export async function serve(options) {
|
|
|
426
3658
|
emitTaskCompleted,
|
|
427
3659
|
};
|
|
428
3660
|
let codexSoftwareAgent = null;
|
|
3661
|
+
let peripherals = [
|
|
3662
|
+
...(relay ? [relay] : []),
|
|
3663
|
+
engineP,
|
|
3664
|
+
interactiveSessionPeripheral,
|
|
3665
|
+
];
|
|
3666
|
+
let runtimePeripheralRecords = [];
|
|
3667
|
+
let requestCompute = async () => ({
|
|
3668
|
+
success: false,
|
|
3669
|
+
error: "Akemon runtime is still initializing. Please retry in a moment.",
|
|
3670
|
+
});
|
|
3671
|
+
let bus = null;
|
|
3672
|
+
const allModules = [];
|
|
429
3673
|
const httpServer = createServer(async (req, res) => {
|
|
430
3674
|
// Suppress noisy polling endpoints from log
|
|
431
3675
|
const isQuiet = req.url === "/self/state" || req.url?.startsWith("/self/state?");
|
|
432
3676
|
if (!isQuiet)
|
|
433
3677
|
console.log(`[http] ${req.method} ${req.url} session=${req.headers["mcp-session-id"] || "none"}`);
|
|
434
3678
|
try {
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
3679
|
+
if (await handleOwnerActionHttpRoute(req, res, {
|
|
3680
|
+
options,
|
|
3681
|
+
workdir,
|
|
3682
|
+
agentName: options.agentName,
|
|
3683
|
+
localPort: options.port,
|
|
3684
|
+
softwareAgent: codexSoftwareAgent,
|
|
3685
|
+
workbench,
|
|
3686
|
+
interactiveSessionPeripheral,
|
|
3687
|
+
requestCompute,
|
|
3688
|
+
})) {
|
|
442
3689
|
return;
|
|
443
3690
|
}
|
|
444
|
-
if (
|
|
445
|
-
await handleSoftwareAgentRunHttp(req, res, {
|
|
446
|
-
options,
|
|
447
|
-
workdir,
|
|
448
|
-
agentName: options.agentName,
|
|
449
|
-
softwareAgent: codexSoftwareAgent,
|
|
450
|
-
});
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (req.url === "/self/software-agent/status" && req.method === "GET") {
|
|
454
|
-
await handleSoftwareAgentStatusHttp(req, res, {
|
|
455
|
-
options,
|
|
456
|
-
softwareAgent: codexSoftwareAgent,
|
|
457
|
-
});
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
const requestPath = req.url?.split("?")[0] || "";
|
|
461
|
-
if (req.method === "GET"
|
|
462
|
-
&& (requestPath === "/self/software-agent/tasks" || requestPath.startsWith("/self/software-agent/tasks/"))) {
|
|
463
|
-
await handleSoftwareAgentTasksHttp(req, res, {
|
|
464
|
-
options,
|
|
465
|
-
workdir,
|
|
466
|
-
agentName: options.agentName,
|
|
467
|
-
});
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (req.url === "/self/software-agent/reset" && req.method === "POST") {
|
|
471
|
-
await handleSoftwareAgentResetHttp(req, res, {
|
|
472
|
-
options,
|
|
473
|
-
softwareAgent: codexSoftwareAgent,
|
|
474
|
-
});
|
|
3691
|
+
if (await handleLocalUiAssetRoute(req, res)) {
|
|
475
3692
|
return;
|
|
476
3693
|
}
|
|
477
3694
|
// Auth check
|
|
@@ -484,89 +3701,13 @@ export async function serve(options) {
|
|
|
484
3701
|
return;
|
|
485
3702
|
}
|
|
486
3703
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const __dirname = dirname(__filename);
|
|
495
|
-
let html;
|
|
496
|
-
try {
|
|
497
|
-
html = await rf(pjoin(__dirname, "live.html"), "utf-8");
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
html = await rf(pjoin(__dirname, "..", "src", "live.html"), "utf-8");
|
|
501
|
-
}
|
|
502
|
-
res.writeHead(200, { "Content-Type": "text/html" }).end(html);
|
|
503
|
-
}
|
|
504
|
-
catch (err) {
|
|
505
|
-
res.writeHead(500).end("Live page not found: " + err.message);
|
|
506
|
-
}
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
// Self-state API (no auth required for local monitoring)
|
|
510
|
-
if (req.url === "/self/state" && req.method === "GET") {
|
|
511
|
-
const state = await getSelfState(workdir, options.agentName);
|
|
512
|
-
// Enrich with credits from relay (best-effort)
|
|
513
|
-
if (relay.connected) {
|
|
514
|
-
try {
|
|
515
|
-
const agents = await relay.listAgents({ online: true, public: true });
|
|
516
|
-
const self = agents.find((a) => a.name === options.agentName);
|
|
517
|
-
state.credits = self?.credits ?? 0;
|
|
518
|
-
state.level = self?.level ?? 0;
|
|
519
|
-
}
|
|
520
|
-
catch {
|
|
521
|
-
state.credits = null;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(state, null, 2));
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
// Revive endpoint — owner brings a forced-offline agent back
|
|
528
|
-
if (req.url === "/self/revive" && req.method === "POST") {
|
|
529
|
-
await reviveAgent(workdir, options.agentName);
|
|
530
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ ok: true, message: "Agent revived. Energy=50, Hunger=50." }));
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
if (req.url?.startsWith("/self/task-history") && req.method === "GET") {
|
|
534
|
-
const url = new URL(req.url, `http://localhost`);
|
|
535
|
-
const limit = parseInt(url.searchParams.get("limit") || "50") || 50;
|
|
536
|
-
const history = await loadTaskHistory(workdir, options.agentName, limit);
|
|
537
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(history, null, 2));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
if (req.url === "/self/directives" && req.method === "GET") {
|
|
541
|
-
const dirs = await loadDirectives(workdir, options.agentName);
|
|
542
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(dirs, null, 2));
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
if (req.url === "/self/canvas" && req.method === "GET") {
|
|
546
|
-
const entries = await loadRecentCanvasEntries(workdir, options.agentName, 10);
|
|
547
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(entries, null, 2));
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (req.url === "/self/conversations" && req.method === "GET") {
|
|
551
|
-
const list = await listConversations(workdir, options.agentName);
|
|
552
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(list, null, 2));
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
if (req.url?.startsWith("/self/conversation/") && req.method === "GET") {
|
|
556
|
-
const convId = decodeURIComponent(req.url.slice("/self/conversation/".length));
|
|
557
|
-
if (!convId) {
|
|
558
|
-
res.writeHead(400).end("Missing conversation ID");
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const conv = await loadConversation(workdir, options.agentName, convId);
|
|
562
|
-
const config = await loadAgentConfig(workdir, options.agentName);
|
|
563
|
-
const budget = config.context_budget ?? 4096;
|
|
564
|
-
const { recentStartIndex } = buildLLMContext(conv, budget);
|
|
565
|
-
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({
|
|
566
|
-
summary: conv.summary,
|
|
567
|
-
rounds: conv.rounds,
|
|
568
|
-
recentStartIndex,
|
|
569
|
-
}, null, 2));
|
|
3704
|
+
if (await handleSelfHttpRoute(req, res, {
|
|
3705
|
+
workdir,
|
|
3706
|
+
agentName: options.agentName,
|
|
3707
|
+
relay,
|
|
3708
|
+
peripherals,
|
|
3709
|
+
runtimePeripheralRecords,
|
|
3710
|
+
})) {
|
|
570
3711
|
return;
|
|
571
3712
|
}
|
|
572
3713
|
// Track publisher ID per session
|
|
@@ -595,8 +3736,9 @@ export async function serve(options) {
|
|
|
595
3736
|
publisherIds.delete(sid);
|
|
596
3737
|
}
|
|
597
3738
|
};
|
|
598
|
-
|
|
599
|
-
|
|
3739
|
+
const activeMcpProxy = await ensureMcpProxy();
|
|
3740
|
+
if (activeMcpProxy) {
|
|
3741
|
+
const proxyServer = createMcpProxyServer(activeMcpProxy, options.agentName);
|
|
600
3742
|
await proxyServer.connect(transport);
|
|
601
3743
|
}
|
|
602
3744
|
else {
|
|
@@ -611,7 +3753,7 @@ export async function serve(options) {
|
|
|
611
3753
|
relayHttp: options.relayHttp,
|
|
612
3754
|
secretKey: options.secretKey,
|
|
613
3755
|
publisherIds,
|
|
614
|
-
relay,
|
|
3756
|
+
relay: relay ?? undefined,
|
|
615
3757
|
}, mcpDeps);
|
|
616
3758
|
await mcpServer.connect(transport);
|
|
617
3759
|
}
|
|
@@ -634,211 +3776,325 @@ export async function serve(options) {
|
|
|
634
3776
|
console.log(`Akemon MCP server running on port ${options.port}`);
|
|
635
3777
|
console.log(`Agent: ${options.agentName}`);
|
|
636
3778
|
console.log(`Workdir: ${workdir}`);
|
|
3779
|
+
console.log("[startup] HTTP ready; runtime initialization continues in background");
|
|
3780
|
+
registerLocalInstance({
|
|
3781
|
+
name: options.agentName,
|
|
3782
|
+
port: options.port,
|
|
3783
|
+
workdir,
|
|
3784
|
+
mode: options.relayHttp ? "relay" : "local-only",
|
|
3785
|
+
relayHttp: options.relayHttp,
|
|
3786
|
+
}).then((record) => {
|
|
3787
|
+
console.log(`[local] Registered ${record.name} at ${record.endpoint}`);
|
|
3788
|
+
}).catch((err) => {
|
|
3789
|
+
console.log(`[local] Instance registry update failed: ${err.message || String(err)}`);
|
|
3790
|
+
});
|
|
3791
|
+
registryHeartbeat = setInterval(() => {
|
|
3792
|
+
touchLocalInstance(options.agentName).catch(() => { });
|
|
3793
|
+
}, 15_000);
|
|
3794
|
+
void ensureMcpProxy();
|
|
637
3795
|
});
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
console.log(`[
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
relay.pullFromRelay(workdir, options.agentName).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
|
|
652
|
-
}
|
|
653
|
-
// V2: Shared module context + persistent EventBus.
|
|
654
|
-
// This is the durable spine for module/peripheral/engine activity; recovery
|
|
655
|
-
// side effects are intentionally deferred until the event schema stabilizes.
|
|
656
|
-
const eventLogDir = join(workdir, ".akemon", "agents", options.agentName, "events");
|
|
657
|
-
await mkdir(eventLogDir, { recursive: true });
|
|
658
|
-
const eventLogPath = join(eventLogDir, "events.jsonl");
|
|
659
|
-
const bus = new PersistentEventBus(new FileEventLog(eventLogPath));
|
|
660
|
-
console.log(`[v2] Event log: ${eventLogPath}`);
|
|
661
|
-
codexSoftwareAgent = new CodexSoftwareAgentPeripheral({
|
|
662
|
-
workdir,
|
|
663
|
-
model: process.env.AKEMON_CODEX_MODEL,
|
|
664
|
-
sandbox: "workspace-write",
|
|
665
|
-
taskLedgerDir: softwareAgentTaskLedgerDir(workdir, options.agentName),
|
|
666
|
-
contextSessionDir: softwareAgentContextSessionDir(workdir, options.agentName),
|
|
667
|
-
envPolicy: options.softwareAgentEnvPolicy,
|
|
668
|
-
envAllowlist: options.softwareAgentEnvAllowlist,
|
|
669
|
-
});
|
|
670
|
-
// Peripheral registry — Core routes by capability
|
|
671
|
-
const peripherals = [relay, engineP, codexSoftwareAgent];
|
|
672
|
-
for (const peripheral of peripherals) {
|
|
673
|
-
await peripheral.start(bus);
|
|
674
|
-
}
|
|
675
|
-
// requestCompute: acquire the engine slot (priority-aware), execute with a
|
|
676
|
-
// hard timeout, and release. The slot release and subprocess kill are both
|
|
677
|
-
// driven by the same AbortController so a stuck engine can't hold the lock.
|
|
678
|
-
async function requestCompute(req) {
|
|
679
|
-
// Load latest agent config for routing table (fast file read, changes rarely)
|
|
680
|
-
const agentCfg = await loadAgentConfig(workdir, options.agentName);
|
|
681
|
-
const routing = agentCfg.engine_routing;
|
|
682
|
-
// user_manual tasks also hold a subscription-CLI semaphore to cap claude CLI concurrency
|
|
683
|
-
const isUserManual = req.origin === "user_manual";
|
|
684
|
-
if (isUserManual) {
|
|
685
|
-
try {
|
|
686
|
-
await engineQueue.acquireUserManualSlot(ENGINE_WAIT_DEADLINE_MS);
|
|
687
|
-
}
|
|
688
|
-
catch (err) {
|
|
689
|
-
return { success: false, error: err.message || "User manual slot timeout" };
|
|
690
|
-
}
|
|
3796
|
+
const initializeRuntime = async () => {
|
|
3797
|
+
const runtimeStartedAt = Date.now();
|
|
3798
|
+
console.log("[startup] Runtime initialization background started");
|
|
3799
|
+
// Initialize agent config + consciousness (world knowledge + bio-state + guide)
|
|
3800
|
+
initAgentConfig(workdir, options.agentName).catch(err => console.log(`[self] Config init failed: ${err}`));
|
|
3801
|
+
loadAgentConfig(workdir, options.agentName).then(c => {
|
|
3802
|
+
const flags = Object.entries(c).filter(([, v]) => v).map(([k]) => k).join(", ");
|
|
3803
|
+
console.log(`[config] Features: ${flags || "(none)"}`);
|
|
3804
|
+
}).catch(() => { });
|
|
3805
|
+
initWorld(workdir, options.agentName, engineP.engineName || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
|
|
3806
|
+
initBioState(workdir, options.agentName).catch(err => console.log(`[self] Bio init failed: ${err}`));
|
|
3807
|
+
if (options.relayHttp) {
|
|
3808
|
+
initGuide(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[self] Guide init failed: ${err}`));
|
|
691
3809
|
}
|
|
692
|
-
|
|
693
|
-
|
|
3810
|
+
// Pull games/notes/pages from relay to restore local data
|
|
3811
|
+
if (relay) {
|
|
3812
|
+
relay.pullFromRelay(workdir, options.agentName).catch(err => console.log(`[sync] Pull from relay failed: ${err}`));
|
|
694
3813
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
3814
|
+
// V2: Shared module context + persistent EventBus.
|
|
3815
|
+
// This is the durable spine for module/peripheral/engine activity; recovery
|
|
3816
|
+
// side effects are intentionally deferred until the event schema stabilizes.
|
|
3817
|
+
const eventLogDir = join(workdir, ".akemon", "agents", options.agentName, "events");
|
|
3818
|
+
await mkdir(eventLogDir, { recursive: true });
|
|
3819
|
+
const eventLogPath = join(eventLogDir, "events.jsonl");
|
|
3820
|
+
bus = new PersistentEventBus(new FileEventLog(eventLogPath));
|
|
3821
|
+
const runtimeBus = bus;
|
|
3822
|
+
console.log(`[v2] Event log: ${eventLogPath}`);
|
|
3823
|
+
codexSoftwareAgent = new CodexSoftwareAgentPeripheral({
|
|
3824
|
+
agentName: options.agentName,
|
|
3825
|
+
workdir,
|
|
3826
|
+
model: process.env.AKEMON_CODEX_MODEL,
|
|
3827
|
+
sandbox: "workspace-write",
|
|
3828
|
+
taskLedgerDir: softwareAgentTaskLedgerDir(workdir, options.agentName),
|
|
3829
|
+
contextSessionDir: softwareAgentContextSessionDir(workdir, options.agentName),
|
|
3830
|
+
workMemoryDir: workMemoryDir(workdir, options.agentName),
|
|
3831
|
+
envPolicy: options.softwareAgentEnvPolicy,
|
|
3832
|
+
envAllowlist: options.softwareAgentEnvAllowlist,
|
|
3833
|
+
});
|
|
3834
|
+
// Peripheral registry — Core routes by capability
|
|
3835
|
+
peripherals = [
|
|
3836
|
+
...(relay ? [relay] : []),
|
|
3837
|
+
engineP,
|
|
3838
|
+
codexSoftwareAgent,
|
|
3839
|
+
interactiveSessionPeripheral,
|
|
3840
|
+
];
|
|
3841
|
+
runtimePeripheralRecords = [
|
|
3842
|
+
...(relay ? [createRuntimePeripheralRecord(relay, {
|
|
3843
|
+
type: "relay",
|
|
3844
|
+
riskLevel: "high",
|
|
3845
|
+
allowedActions: ["relay:read", "relay:publish", "relay:order", "relay:market"],
|
|
3846
|
+
url: options.relayHttp,
|
|
3847
|
+
exploreDescription: "Summarizes relay orders, tasks, market, and public network feed.",
|
|
3848
|
+
status: "available",
|
|
3849
|
+
})] : []),
|
|
3850
|
+
createRuntimePeripheralRecord(engineP, {
|
|
3851
|
+
type: "engine",
|
|
3852
|
+
riskLevel: "low",
|
|
3853
|
+
allowedActions: ["compute:text"],
|
|
3854
|
+
startCommand: engineP.engineName,
|
|
3855
|
+
exploreDescription: "Primary text compute engine used through Akemon's engine queue.",
|
|
3856
|
+
status: "available",
|
|
3857
|
+
}),
|
|
3858
|
+
createRuntimePeripheralRecord(codexSoftwareAgent, {
|
|
3859
|
+
type: "software-agent",
|
|
3860
|
+
riskLevel: "high",
|
|
3861
|
+
allowedActions: ["repo:inspect", "repo:edit", "command:run", "skill:use"],
|
|
3862
|
+
startCommand: "codex exec",
|
|
3863
|
+
exploreDescription: "Owner-local Codex CLI software-agent peripheral for repository work.",
|
|
3864
|
+
status: "available",
|
|
3865
|
+
}),
|
|
3866
|
+
createRuntimePeripheralRecord(interactiveSessionPeripheral, {
|
|
3867
|
+
type: "interactive-session",
|
|
3868
|
+
riskLevel: "high",
|
|
3869
|
+
allowedActions: [
|
|
3870
|
+
"session:list",
|
|
3871
|
+
"session:inspect",
|
|
3872
|
+
"session:start",
|
|
3873
|
+
"session:input",
|
|
3874
|
+
"session:resize",
|
|
3875
|
+
"session:stop",
|
|
3876
|
+
"session:capture-output",
|
|
3877
|
+
],
|
|
3878
|
+
exploreDescription: "Owner-local interactive sessions for Codex, Claude, shell, cursor, and custom PTY processes.",
|
|
3879
|
+
status: "available",
|
|
3880
|
+
}),
|
|
3881
|
+
];
|
|
705
3882
|
try {
|
|
706
|
-
|
|
707
|
-
emitTokenUsage(prompt.length, response.length);
|
|
708
|
-
return { success: true, response };
|
|
3883
|
+
await upsertPeripheralRecords(options.agentName, runtimePeripheralRecords.map((record) => ({ ...record, status: "configured" })));
|
|
709
3884
|
}
|
|
710
|
-
catch (
|
|
711
|
-
|
|
712
|
-
? `Engine execution timeout (${Math.round(ENGINE_EXEC_TIMEOUT_MS / 60000)} min)`
|
|
713
|
-
: (err.message || String(err));
|
|
714
|
-
return { success: false, error: msg };
|
|
3885
|
+
catch (error) {
|
|
3886
|
+
logBestEffortError("peripheral registry sync", error);
|
|
715
3887
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
engineQueue.release();
|
|
719
|
-
if (isUserManual)
|
|
720
|
-
engineQueue.releaseUserManualSlot();
|
|
3888
|
+
for (const peripheral of peripherals) {
|
|
3889
|
+
await peripheral.start(runtimeBus);
|
|
721
3890
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (c)
|
|
739
|
-
contributions.push(c);
|
|
3891
|
+
// requestCompute: acquire the engine slot (priority-aware), execute with a
|
|
3892
|
+
// hard timeout, and release. The slot release and subprocess kill are both
|
|
3893
|
+
// driven by the same AbortController so a stuck engine can't hold the lock.
|
|
3894
|
+
requestCompute = async (req) => {
|
|
3895
|
+
// Load latest agent config for routing table (fast file read, changes rarely)
|
|
3896
|
+
const agentCfg = await loadAgentConfig(workdir, options.agentName);
|
|
3897
|
+
const routing = agentCfg.engine_routing;
|
|
3898
|
+
const engineResources = agentCfg.engine_resources;
|
|
3899
|
+
// user_manual tasks also hold a subscription-CLI semaphore to cap claude CLI concurrency
|
|
3900
|
+
const isUserManual = req.origin === "user_manual";
|
|
3901
|
+
if (isUserManual) {
|
|
3902
|
+
try {
|
|
3903
|
+
await engineQueue.acquireUserManualSlot(ENGINE_WAIT_DEADLINE_MS);
|
|
3904
|
+
}
|
|
3905
|
+
catch (err) {
|
|
3906
|
+
return { success: false, error: err.message || "User manual slot timeout" };
|
|
740
3907
|
}
|
|
741
3908
|
}
|
|
742
|
-
|
|
743
|
-
|
|
3909
|
+
try {
|
|
3910
|
+
await engineQueue.acquire(req.priority, ENGINE_WAIT_DEADLINE_MS);
|
|
3911
|
+
}
|
|
3912
|
+
catch (err) {
|
|
3913
|
+
if (isUserManual)
|
|
3914
|
+
engineQueue.releaseUserManualSlot();
|
|
3915
|
+
return { success: false, error: err.message || "Engine busy timeout" };
|
|
3916
|
+
}
|
|
3917
|
+
const prompt = req.context
|
|
3918
|
+
? `${req.context}\n\n---\n\n${req.question}`
|
|
3919
|
+
: req.question;
|
|
3920
|
+
const abortController = new AbortController();
|
|
3921
|
+
const timer = setTimeout(() => abortController.abort(), ENGINE_EXEC_TIMEOUT_MS);
|
|
3922
|
+
const startedAt = Date.now();
|
|
3923
|
+
let outputChars = 0;
|
|
3924
|
+
let success = false;
|
|
3925
|
+
let errorSummary;
|
|
3926
|
+
let result;
|
|
3927
|
+
try {
|
|
3928
|
+
const response = await runEngine(options.engine || "claude", options.model, options.allowAll, prompt, workdir, req.tools, req.relay, abortController.signal, req.origin, routing, req.taskId, {
|
|
3929
|
+
...req.engineHints,
|
|
3930
|
+
...(req.taskKind ? { taskKind: req.taskKind } : {}),
|
|
3931
|
+
}, engineResources);
|
|
3932
|
+
success = true;
|
|
3933
|
+
outputChars = response.length;
|
|
3934
|
+
emitTokenUsage(prompt.length, response.length);
|
|
3935
|
+
result = { success: true, response };
|
|
3936
|
+
}
|
|
3937
|
+
catch (err) {
|
|
3938
|
+
const msg = abortController.signal.aborted
|
|
3939
|
+
? `Engine execution timeout (${Math.round(ENGINE_EXEC_TIMEOUT_MS / 60000)} min)`
|
|
3940
|
+
: (err.message || String(err));
|
|
3941
|
+
errorSummary = msg;
|
|
3942
|
+
result = { success: false, error: msg };
|
|
3943
|
+
}
|
|
3944
|
+
finally {
|
|
3945
|
+
clearTimeout(timer);
|
|
3946
|
+
engineQueue.release();
|
|
3947
|
+
if (isUserManual)
|
|
3948
|
+
engineQueue.releaseUserManualSlot();
|
|
3949
|
+
const audit = await appendComputeAuditEvent({
|
|
3950
|
+
agentName: options.agentName,
|
|
3951
|
+
workdir,
|
|
3952
|
+
req,
|
|
3953
|
+
promptChars: prompt.length,
|
|
3954
|
+
outputChars,
|
|
3955
|
+
durationMs: Date.now() - startedAt,
|
|
3956
|
+
success,
|
|
3957
|
+
error: errorSummary,
|
|
3958
|
+
invocation: _engineP?.lastInvocation || null,
|
|
3959
|
+
});
|
|
3960
|
+
result.audit = audit;
|
|
3961
|
+
}
|
|
3962
|
+
return result;
|
|
3963
|
+
};
|
|
3964
|
+
const moduleCtx = {
|
|
3965
|
+
bus: runtimeBus,
|
|
3966
|
+
agentName: options.agentName,
|
|
3967
|
+
workdir,
|
|
3968
|
+
getPeripherals: (capability) => peripherals.filter(p => p.capabilities.includes(capability)),
|
|
3969
|
+
sendTo: async (capability, signal) => {
|
|
3970
|
+
const p = peripherals.find(p => p.capabilities.includes(capability));
|
|
3971
|
+
return p ? p.send(signal) : null;
|
|
3972
|
+
},
|
|
3973
|
+
requestCompute,
|
|
3974
|
+
getPromptContributions: () => {
|
|
3975
|
+
const contributions = [];
|
|
3976
|
+
for (const m of allModules) {
|
|
3977
|
+
if (m.promptContribution) {
|
|
3978
|
+
const c = m.promptContribution();
|
|
3979
|
+
if (c)
|
|
3980
|
+
contributions.push(c);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
return contributions;
|
|
3984
|
+
},
|
|
3985
|
+
};
|
|
3986
|
+
// V2: Conditionally load modules based on --with/--without
|
|
3987
|
+
const enabled = options.enabledModules ?? ["biostate", "memory", "role", "task", "social", "longterm", "reflection", "script"];
|
|
3988
|
+
const loadedModules = [];
|
|
3989
|
+
_bus = bus;
|
|
3990
|
+
const startModule = async (id, create, afterCreate) => {
|
|
3991
|
+
if (!enabled.includes(id))
|
|
3992
|
+
return;
|
|
3993
|
+
const startedAt = Date.now();
|
|
3994
|
+
console.log(`[v2] Module init: ${id}...`);
|
|
3995
|
+
const module = create();
|
|
3996
|
+
afterCreate?.(module);
|
|
3997
|
+
await module.start(moduleCtx);
|
|
3998
|
+
allModules.push(module);
|
|
3999
|
+
loadedModules.push(id);
|
|
4000
|
+
console.log(`[v2] Module ready: ${id} (${Date.now() - startedAt}ms)`);
|
|
4001
|
+
};
|
|
4002
|
+
const startModules = async () => {
|
|
4003
|
+
const startedAt = Date.now();
|
|
4004
|
+
console.log(`[v2] Module init background: ${enabled.join(", ") || "(none)"}`);
|
|
4005
|
+
await startModule("biostate", () => {
|
|
4006
|
+
const bioModule = new BioStateModule();
|
|
4007
|
+
options.bioModule = bioModule;
|
|
4008
|
+
return bioModule;
|
|
4009
|
+
});
|
|
4010
|
+
await startModule("memory", () => {
|
|
4011
|
+
const memoryModule = new MemoryModule();
|
|
4012
|
+
if (options.cycleInterval)
|
|
4013
|
+
memoryModule.cycleIntervalMs = options.cycleInterval * 60 * 1000;
|
|
4014
|
+
options.memoryModule = memoryModule;
|
|
4015
|
+
return memoryModule;
|
|
4016
|
+
});
|
|
4017
|
+
await startModule("role", () => new RoleModule());
|
|
4018
|
+
await startModule("task", () => {
|
|
4019
|
+
const taskModule = new TaskModule();
|
|
4020
|
+
taskModule.relayHttp = options.relayHttp || "";
|
|
4021
|
+
taskModule.secretKey = options.secretKey || "";
|
|
4022
|
+
taskModule.engine = options.engine || "";
|
|
4023
|
+
taskModule.model = options.model;
|
|
4024
|
+
taskModule.allowAll = options.allowAll;
|
|
4025
|
+
taskModule.notifyUrl = options.notifyUrl;
|
|
4026
|
+
_taskModule = taskModule;
|
|
4027
|
+
return taskModule;
|
|
4028
|
+
});
|
|
4029
|
+
await startModule("social", () => new SocialModule());
|
|
4030
|
+
await startModule("longterm", () => new LongTermModule());
|
|
4031
|
+
await startModule("reflection", () => new ReflectionModule());
|
|
4032
|
+
await startModule("script", () => {
|
|
4033
|
+
const scriptModule = new ScriptModule();
|
|
4034
|
+
scriptModule.scriptName = options.scriptName || "daily-life";
|
|
4035
|
+
scriptModule.relayHttp = options.relayHttp || "";
|
|
4036
|
+
scriptModule.secretKey = options.secretKey || "";
|
|
4037
|
+
return scriptModule;
|
|
4038
|
+
});
|
|
4039
|
+
console.log(`[v2] Modules: ${loadedModules.join(", ") || "(none)"} (${Date.now() - startedAt}ms)`);
|
|
4040
|
+
// V2: Emit agent start lifecycle event after modules have subscribed.
|
|
4041
|
+
runtimeBus.emit(SIG.AGENT_START, sig(SIG.AGENT_START, {
|
|
4042
|
+
agentName: options.agentName,
|
|
4043
|
+
engine: engineP.engineName,
|
|
4044
|
+
modules: loadedModules,
|
|
4045
|
+
}));
|
|
4046
|
+
};
|
|
4047
|
+
setTimeout(() => {
|
|
4048
|
+
startModules().catch((error) => {
|
|
4049
|
+
console.error(`[v2] Module init failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4050
|
+
process.exit(1);
|
|
4051
|
+
});
|
|
4052
|
+
}, 100);
|
|
4053
|
+
console.log(`[startup] Runtime core ready (${Date.now() - runtimeStartedAt}ms)`);
|
|
744
4054
|
};
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
await bioModule.start(moduleCtx);
|
|
752
|
-
options.bioModule = bioModule;
|
|
753
|
-
allModules.push(bioModule);
|
|
754
|
-
loadedModules.push("biostate");
|
|
755
|
-
}
|
|
756
|
-
if (enabled.includes("memory")) {
|
|
757
|
-
const memoryModule = new MemoryModule();
|
|
758
|
-
if (options.cycleInterval)
|
|
759
|
-
memoryModule.cycleIntervalMs = options.cycleInterval * 60 * 1000;
|
|
760
|
-
await memoryModule.start(moduleCtx);
|
|
761
|
-
options.memoryModule = memoryModule;
|
|
762
|
-
allModules.push(memoryModule);
|
|
763
|
-
loadedModules.push("memory");
|
|
764
|
-
}
|
|
765
|
-
if (enabled.includes("role")) {
|
|
766
|
-
const roleModule = new RoleModule();
|
|
767
|
-
await roleModule.start(moduleCtx);
|
|
768
|
-
allModules.push(roleModule);
|
|
769
|
-
loadedModules.push("role");
|
|
770
|
-
}
|
|
771
|
-
if (enabled.includes("task")) {
|
|
772
|
-
const taskModule = new TaskModule();
|
|
773
|
-
taskModule.relayHttp = options.relayHttp || "";
|
|
774
|
-
taskModule.secretKey = options.secretKey || "";
|
|
775
|
-
taskModule.engine = options.engine || "";
|
|
776
|
-
taskModule.model = options.model;
|
|
777
|
-
taskModule.allowAll = options.allowAll;
|
|
778
|
-
taskModule.notifyUrl = options.notifyUrl;
|
|
779
|
-
await taskModule.start(moduleCtx);
|
|
780
|
-
_taskModule = taskModule;
|
|
781
|
-
allModules.push(taskModule);
|
|
782
|
-
loadedModules.push("task");
|
|
783
|
-
}
|
|
784
|
-
if (enabled.includes("social")) {
|
|
785
|
-
const socialModule = new SocialModule();
|
|
786
|
-
await socialModule.start(moduleCtx);
|
|
787
|
-
allModules.push(socialModule);
|
|
788
|
-
loadedModules.push("social");
|
|
789
|
-
}
|
|
790
|
-
if (enabled.includes("longterm")) {
|
|
791
|
-
const longtermModule = new LongTermModule();
|
|
792
|
-
await longtermModule.start(moduleCtx);
|
|
793
|
-
allModules.push(longtermModule);
|
|
794
|
-
loadedModules.push("longterm");
|
|
795
|
-
}
|
|
796
|
-
if (enabled.includes("reflection")) {
|
|
797
|
-
const reflectionModule = new ReflectionModule();
|
|
798
|
-
await reflectionModule.start(moduleCtx);
|
|
799
|
-
allModules.push(reflectionModule);
|
|
800
|
-
loadedModules.push("reflection");
|
|
801
|
-
}
|
|
802
|
-
if (enabled.includes("script")) {
|
|
803
|
-
const scriptModule = new ScriptModule();
|
|
804
|
-
scriptModule.scriptName = options.scriptName || "daily-life";
|
|
805
|
-
scriptModule.relayHttp = options.relayHttp || "";
|
|
806
|
-
scriptModule.secretKey = options.secretKey || "";
|
|
807
|
-
await scriptModule.start(moduleCtx);
|
|
808
|
-
allModules.push(scriptModule);
|
|
809
|
-
loadedModules.push("script");
|
|
810
|
-
}
|
|
811
|
-
console.log(`[v2] Modules: ${loadedModules.join(", ") || "(none)"}`);
|
|
812
|
-
// Inject peripherals into options
|
|
813
|
-
options.relay = relay;
|
|
814
|
-
options.enginePeripheral = engineP;
|
|
815
|
-
_engineP = engineP;
|
|
816
|
-
_bus = bus;
|
|
817
|
-
// V2: Emit agent start lifecycle event
|
|
818
|
-
bus.emit(SIG.AGENT_START, sig(SIG.AGENT_START, {
|
|
819
|
-
agentName: options.agentName,
|
|
820
|
-
engine: options.engine,
|
|
821
|
-
modules: loadedModules,
|
|
822
|
-
}));
|
|
4055
|
+
setTimeout(() => {
|
|
4056
|
+
initializeRuntime().catch((error) => {
|
|
4057
|
+
console.error(`[startup] Runtime init failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4058
|
+
process.exit(1);
|
|
4059
|
+
});
|
|
4060
|
+
}, 0);
|
|
823
4061
|
// Graceful shutdown — kill engine children first, then stop modules.
|
|
824
4062
|
// Engine children are killed via process group signal so any sub-forks
|
|
825
4063
|
// (e.g. opencode's internal Node workers) are also terminated.
|
|
826
4064
|
const shutdown = async () => {
|
|
827
4065
|
console.log("[v2] Shutting down...");
|
|
4066
|
+
if (registryHeartbeat)
|
|
4067
|
+
clearInterval(registryHeartbeat);
|
|
4068
|
+
try {
|
|
4069
|
+
await unregisterLocalInstance(options.agentName);
|
|
4070
|
+
}
|
|
4071
|
+
catch (error) {
|
|
4072
|
+
logBestEffortError("local instance unregister", error);
|
|
4073
|
+
}
|
|
828
4074
|
if (_engineP)
|
|
829
4075
|
_engineP.killAllChildren();
|
|
830
|
-
bus
|
|
4076
|
+
bus?.emit(SIG.AGENT_STOP, sig(SIG.AGENT_STOP, {}));
|
|
831
4077
|
for (const m of allModules) {
|
|
832
4078
|
try {
|
|
833
4079
|
await m.stop();
|
|
834
4080
|
}
|
|
835
|
-
catch {
|
|
4081
|
+
catch (error) {
|
|
4082
|
+
logBestEffortError(`module stop ${m.id}`, error);
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
try {
|
|
4086
|
+
await workbench.shutdown();
|
|
4087
|
+
}
|
|
4088
|
+
catch (error) {
|
|
4089
|
+
logBestEffortError("workbench shutdown", error);
|
|
836
4090
|
}
|
|
837
4091
|
try {
|
|
838
4092
|
await codexSoftwareAgent?.stop();
|
|
839
4093
|
}
|
|
840
|
-
catch {
|
|
841
|
-
|
|
4094
|
+
catch (error) {
|
|
4095
|
+
logBestEffortError("software-agent stop", error);
|
|
4096
|
+
}
|
|
4097
|
+
bus?.getLog().close();
|
|
842
4098
|
process.exit(0);
|
|
843
4099
|
};
|
|
844
4100
|
process.on("SIGINT", shutdown);
|