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
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import { buildLLMContext, loadConversation } from "./context.js";
|
|
2
|
+
import { MEMORY_ACTION_PROPOSAL_GUIDE, createMemoryProposal, describePendingMemoryProposalsForCoreCm, executeMemoryActionProposalWithResult, formatCurrentMemoryForCoreCm, } from "./memory-proposal.js";
|
|
3
|
+
import { WORKBENCH_ACTION_PROPOSAL_GUIDE, WORKBENCH_PERIPHERAL_GUIDE, } from "./workbench-peripheral-guide.js";
|
|
4
|
+
const DEFAULT_SEND_INPUT_WAIT_MS = 600;
|
|
5
|
+
const MAX_SEND_INPUT_WAIT_MS = 10_000;
|
|
6
|
+
const INTERACTIVE_SESSION_PERIPHERAL = "interactive-session";
|
|
7
|
+
export async function runLocalCoreCmChat(input) {
|
|
8
|
+
const memoryContext = await buildCoreCmMemoryContext({
|
|
9
|
+
agentName: input.agentName,
|
|
10
|
+
workdir: input.workdir || "",
|
|
11
|
+
});
|
|
12
|
+
const conversationContext = await buildCoreCmConversationContext({
|
|
13
|
+
agentName: input.agentName,
|
|
14
|
+
workdir: input.workdir || "",
|
|
15
|
+
conversationId: input.conversationId,
|
|
16
|
+
});
|
|
17
|
+
const context = [
|
|
18
|
+
"[Akemon Core CM]",
|
|
19
|
+
`You are the Core Cognitive Module for ${input.agentName}.`,
|
|
20
|
+
"You are handling the owner's local Akemon chat message.",
|
|
21
|
+
"",
|
|
22
|
+
"Architecture boundaries:",
|
|
23
|
+
"- Core CM handles conversation, task judgment, delegation decisions, and natural-language explanation.",
|
|
24
|
+
"- Memory CM only participates when memory work is needed. Do not write memory here; mention possible memory changes naturally if they matter.",
|
|
25
|
+
"- Workbench is a local presentation/control surface, not a Cognitive Module.",
|
|
26
|
+
"- Claude, Codex, shell, browser, files, services, and custom PTY sessions are peripherals used through adapters.",
|
|
27
|
+
"- Owner requests to remember something are Akemon memory requests. Do not interpret them as CLAUDE.md, ChatGPT memory, Claude memory, or external agent memory unless the owner explicitly asks for that target.",
|
|
28
|
+
"- Use Akemon memory context when it is relevant to task routing, project preferences, and owner-facing explanations.",
|
|
29
|
+
"- Do not claim that you started, stopped, or sent input to a peripheral unless the observation says that already happened.",
|
|
30
|
+
"- If you request a peripheral action, Akemon will execute it and return the observation to you for final owner-facing narration and memory judgment.",
|
|
31
|
+
"",
|
|
32
|
+
formatOwnerLanguagePolicy(input.ownerLanguage),
|
|
33
|
+
"",
|
|
34
|
+
"Process visibility:",
|
|
35
|
+
"- Include processNote in JSON responses. This is the short CM-area narration for the owner.",
|
|
36
|
+
"- processNote must be natural language in the configured owner-facing language.",
|
|
37
|
+
"- processNote should explain what Core CM is doing and why. Do not expose JSON keys, taskKind, adapter internals, token counts, or event ids unless the owner explicitly asks for debug details.",
|
|
38
|
+
"",
|
|
39
|
+
WORKBENCH_PERIPHERAL_GUIDE,
|
|
40
|
+
"",
|
|
41
|
+
WORKBENCH_ACTION_PROPOSAL_GUIDE,
|
|
42
|
+
"",
|
|
43
|
+
MEMORY_ACTION_PROPOSAL_GUIDE,
|
|
44
|
+
"",
|
|
45
|
+
"Current interactive session context:",
|
|
46
|
+
describeInteractiveSessionsForCoreCm(input.interactiveSessions),
|
|
47
|
+
"",
|
|
48
|
+
"Akemon memory context:",
|
|
49
|
+
memoryContext,
|
|
50
|
+
"",
|
|
51
|
+
"Recent owner/Akemon chat context:",
|
|
52
|
+
conversationContext,
|
|
53
|
+
"",
|
|
54
|
+
input.workdir ? `Current workdir: ${input.workdir}` : "Current workdir: unknown",
|
|
55
|
+
].join("\n");
|
|
56
|
+
const question = [
|
|
57
|
+
"Owner message:",
|
|
58
|
+
input.text,
|
|
59
|
+
"",
|
|
60
|
+
"Decide whether to answer directly, request a Workbench peripheral action, create an Akemon memory proposal, or act on a pending memory suggestion.",
|
|
61
|
+
"Return only JSON matching the relevant contract above.",
|
|
62
|
+
].join("\n");
|
|
63
|
+
const result = await input.requestCompute({
|
|
64
|
+
context,
|
|
65
|
+
question,
|
|
66
|
+
taskKind: "core_action_proposal",
|
|
67
|
+
purpose: "Core CM decides whether to answer directly, use Workbench, create a memory proposal, or act on pending memory.",
|
|
68
|
+
priority: "normal",
|
|
69
|
+
origin: "user_manual",
|
|
70
|
+
engineHints: {
|
|
71
|
+
requiredCapabilities: ["chat", "json"],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
return {
|
|
76
|
+
output: shouldUseChineseOwnerText(input.ownerLanguage, input.text)
|
|
77
|
+
? `Core CM 没能完成回复,因为 engine 返回了错误:${result.error || "unknown error"}`
|
|
78
|
+
: `I could not complete the Core CM response because the engine returned an error: ${result.error || "unknown error"}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const proposal = parseCoreCmActionProposal(result.response);
|
|
82
|
+
if (!proposal) {
|
|
83
|
+
return {
|
|
84
|
+
output: result.response?.trim() || (shouldUseChineseOwnerText(input.ownerLanguage, input.text)
|
|
85
|
+
? "Core CM 没能完成回复,因为 engine 返回了空内容。"
|
|
86
|
+
: "I could not complete the Core CM response because the engine returned an empty response."),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (proposal.kind === "answer") {
|
|
90
|
+
return {
|
|
91
|
+
output: proposal.answer.trim() || (shouldUseChineseOwnerText(input.ownerLanguage, input.text)
|
|
92
|
+
? "我现在还没有有用的回答。"
|
|
93
|
+
: "I do not have a useful answer yet."),
|
|
94
|
+
processNotes: collectProcessNotes(proposal.processNote),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (proposal.kind === "memory_action") {
|
|
98
|
+
const memoryAction = await executeMemoryActionProposalWithResult({
|
|
99
|
+
agentName: input.agentName,
|
|
100
|
+
workdir: input.workdir || "",
|
|
101
|
+
proposal,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
output: memoryAction.output,
|
|
105
|
+
memoryAction,
|
|
106
|
+
processNotes: collectProcessNotes(proposal.processNote),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (proposal.kind === "memory_proposal") {
|
|
110
|
+
const memoryProposal = createMemoryProposal({
|
|
111
|
+
agentName: input.agentName,
|
|
112
|
+
workdir: input.workdir || "",
|
|
113
|
+
source: "core-cm-direct-memory-request",
|
|
114
|
+
content: proposal.memory,
|
|
115
|
+
reason: proposal.reason,
|
|
116
|
+
}) || undefined;
|
|
117
|
+
return {
|
|
118
|
+
output: proposal.answer.trim() || "I prepared that as a memory suggestion.",
|
|
119
|
+
memoryProposal,
|
|
120
|
+
processNotes: collectProcessNotes(proposal.processNote),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const observation = await input.executeInteractiveSessionActionProposal(proposal);
|
|
124
|
+
const reviewed = await narrateInteractiveSessionObservationWithCoreCm(input, proposal, observation);
|
|
125
|
+
return {
|
|
126
|
+
...reviewed,
|
|
127
|
+
processNotes: collectProcessNotes(proposal.processNote, ...(reviewed.processNotes || [])),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function parseCoreCmActionProposal(raw) {
|
|
131
|
+
return parseMemoryCreateProposal(raw) || parseMemoryActionProposal(raw) || parseInteractiveSessionActionProposal(raw);
|
|
132
|
+
}
|
|
133
|
+
function parseMemoryCreateProposal(raw) {
|
|
134
|
+
const text = raw?.trim();
|
|
135
|
+
if (!text)
|
|
136
|
+
return null;
|
|
137
|
+
const jsonText = extractJsonObject(text);
|
|
138
|
+
if (!jsonText)
|
|
139
|
+
return null;
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = JSON.parse(jsonText);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (!parsed || typeof parsed !== "object")
|
|
148
|
+
return null;
|
|
149
|
+
const value = parsed;
|
|
150
|
+
if (value.kind !== "memory_proposal" || typeof value.answer !== "string")
|
|
151
|
+
return null;
|
|
152
|
+
const memory = value.memory && typeof value.memory === "object"
|
|
153
|
+
? value.memory
|
|
154
|
+
: {};
|
|
155
|
+
return {
|
|
156
|
+
kind: "memory_proposal",
|
|
157
|
+
answer: value.answer,
|
|
158
|
+
memory: {
|
|
159
|
+
summary: typeof memory.summary === "string" ? memory.summary : undefined,
|
|
160
|
+
self: Array.isArray(memory.self) ? normalizeStringArray(memory.self) : undefined,
|
|
161
|
+
work: Array.isArray(memory.work) ? normalizeStringArray(memory.work) : undefined,
|
|
162
|
+
},
|
|
163
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function parseMemoryActionProposal(raw) {
|
|
167
|
+
const text = raw?.trim();
|
|
168
|
+
if (!text)
|
|
169
|
+
return null;
|
|
170
|
+
const jsonText = extractJsonObject(text);
|
|
171
|
+
if (!jsonText)
|
|
172
|
+
return null;
|
|
173
|
+
let parsed;
|
|
174
|
+
try {
|
|
175
|
+
parsed = JSON.parse(jsonText);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (!parsed || typeof parsed !== "object")
|
|
181
|
+
return null;
|
|
182
|
+
const value = parsed;
|
|
183
|
+
if (value.kind !== "memory_action")
|
|
184
|
+
return null;
|
|
185
|
+
if (!["list_pending", "show_memory", "accept_pending", "reject_pending", "revise_pending"].includes(value.action))
|
|
186
|
+
return null;
|
|
187
|
+
const scope = value.scope === "self" || value.scope === "work" || value.scope === "all"
|
|
188
|
+
? value.scope
|
|
189
|
+
: undefined;
|
|
190
|
+
const memory = value.memory && typeof value.memory === "object"
|
|
191
|
+
? value.memory
|
|
192
|
+
: {};
|
|
193
|
+
return {
|
|
194
|
+
kind: "memory_action",
|
|
195
|
+
action: value.action,
|
|
196
|
+
proposalId: typeof value.proposalId === "string" ? value.proposalId : undefined,
|
|
197
|
+
scope,
|
|
198
|
+
...(value.action === "revise_pending" ? {
|
|
199
|
+
memory: {
|
|
200
|
+
summary: typeof memory.summary === "string" ? memory.summary : undefined,
|
|
201
|
+
self: Array.isArray(memory.self) ? normalizeStringArray(memory.self) : undefined,
|
|
202
|
+
work: Array.isArray(memory.work) ? normalizeStringArray(memory.work) : undefined,
|
|
203
|
+
},
|
|
204
|
+
} : {}),
|
|
205
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
206
|
+
processNote: parseProcessNote(value),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function parseInteractiveSessionActionProposal(raw) {
|
|
210
|
+
const text = raw?.trim();
|
|
211
|
+
if (!text)
|
|
212
|
+
return null;
|
|
213
|
+
const jsonText = extractJsonObject(text);
|
|
214
|
+
if (!jsonText)
|
|
215
|
+
return null;
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
parsed = JSON.parse(jsonText);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (!parsed || typeof parsed !== "object")
|
|
224
|
+
return null;
|
|
225
|
+
const value = parsed;
|
|
226
|
+
if (value.kind === "answer" && typeof value.answer === "string") {
|
|
227
|
+
return { kind: "answer", answer: value.answer, processNote: parseProcessNote(value) };
|
|
228
|
+
}
|
|
229
|
+
const peripheral = parseInteractiveSessionProposalPeripheral(value.peripheral);
|
|
230
|
+
if (value.kind !== "use_peripheral" || !peripheral)
|
|
231
|
+
return null;
|
|
232
|
+
if (value.capability === "list_sessions") {
|
|
233
|
+
return {
|
|
234
|
+
kind: "use_peripheral",
|
|
235
|
+
peripheral,
|
|
236
|
+
capability: "list_sessions",
|
|
237
|
+
args: {},
|
|
238
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
239
|
+
processNote: parseProcessNote(value),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (value.capability === "inspect_session" && value.args && typeof value.args === "object") {
|
|
243
|
+
const args = value.args;
|
|
244
|
+
if (typeof args.sessionId !== "string")
|
|
245
|
+
return null;
|
|
246
|
+
return {
|
|
247
|
+
kind: "use_peripheral",
|
|
248
|
+
peripheral,
|
|
249
|
+
capability: "inspect_session",
|
|
250
|
+
args: {
|
|
251
|
+
sessionId: args.sessionId,
|
|
252
|
+
},
|
|
253
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
254
|
+
processNote: parseProcessNote(value),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (value.capability === "start_session" && value.args && typeof value.args === "object") {
|
|
258
|
+
const args = value.args;
|
|
259
|
+
if (typeof args.tool !== "string")
|
|
260
|
+
return null;
|
|
261
|
+
return {
|
|
262
|
+
kind: "use_peripheral",
|
|
263
|
+
peripheral,
|
|
264
|
+
capability: "start_session",
|
|
265
|
+
args: {
|
|
266
|
+
tool: args.tool,
|
|
267
|
+
command: typeof args.command === "string" ? args.command : undefined,
|
|
268
|
+
args: Array.isArray(args.args) ? args.args : undefined,
|
|
269
|
+
workdir: typeof args.workdir === "string" ? args.workdir : undefined,
|
|
270
|
+
label: typeof args.label === "string" ? args.label : undefined,
|
|
271
|
+
inputMode: args.inputMode === "line" || args.inputMode === "tui" ? args.inputMode : undefined,
|
|
272
|
+
},
|
|
273
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
274
|
+
processNote: parseProcessNote(value),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (value.capability === "send_input" && value.args && typeof value.args === "object") {
|
|
278
|
+
const args = value.args;
|
|
279
|
+
if (typeof args.sessionId !== "string" || typeof args.input !== "string")
|
|
280
|
+
return null;
|
|
281
|
+
return {
|
|
282
|
+
kind: "use_peripheral",
|
|
283
|
+
peripheral,
|
|
284
|
+
capability: "send_input",
|
|
285
|
+
args: {
|
|
286
|
+
sessionId: args.sessionId,
|
|
287
|
+
input: args.input,
|
|
288
|
+
waitMs: normalizeSendInputWaitMs(args.waitMs),
|
|
289
|
+
},
|
|
290
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
291
|
+
processNote: parseProcessNote(value),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (value.capability === "set_input_mode" && value.args && typeof value.args === "object") {
|
|
295
|
+
const args = value.args;
|
|
296
|
+
if (typeof args.sessionId !== "string" || (args.inputMode !== "line" && args.inputMode !== "tui"))
|
|
297
|
+
return null;
|
|
298
|
+
return {
|
|
299
|
+
kind: "use_peripheral",
|
|
300
|
+
peripheral,
|
|
301
|
+
capability: "set_input_mode",
|
|
302
|
+
args: {
|
|
303
|
+
sessionId: args.sessionId,
|
|
304
|
+
inputMode: args.inputMode,
|
|
305
|
+
},
|
|
306
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
307
|
+
processNote: parseProcessNote(value),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (value.capability === "stop_session" && value.args && typeof value.args === "object") {
|
|
311
|
+
const args = value.args;
|
|
312
|
+
if (typeof args.sessionId !== "string")
|
|
313
|
+
return null;
|
|
314
|
+
return {
|
|
315
|
+
kind: "use_peripheral",
|
|
316
|
+
peripheral,
|
|
317
|
+
capability: "stop_session",
|
|
318
|
+
args: {
|
|
319
|
+
sessionId: args.sessionId,
|
|
320
|
+
},
|
|
321
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
322
|
+
processNote: parseProcessNote(value),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (value.capability === "resize_session" && value.args && typeof value.args === "object") {
|
|
326
|
+
const args = value.args;
|
|
327
|
+
if (typeof args.sessionId !== "string" || typeof args.cols !== "number" || typeof args.rows !== "number")
|
|
328
|
+
return null;
|
|
329
|
+
return {
|
|
330
|
+
kind: "use_peripheral",
|
|
331
|
+
peripheral,
|
|
332
|
+
capability: "resize_session",
|
|
333
|
+
args: {
|
|
334
|
+
sessionId: args.sessionId,
|
|
335
|
+
cols: args.cols,
|
|
336
|
+
rows: args.rows,
|
|
337
|
+
},
|
|
338
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
339
|
+
processNote: parseProcessNote(value),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (value.capability === "capture_output" && value.args && typeof value.args === "object") {
|
|
343
|
+
const args = value.args;
|
|
344
|
+
if (typeof args.sessionId !== "string")
|
|
345
|
+
return null;
|
|
346
|
+
return {
|
|
347
|
+
kind: "use_peripheral",
|
|
348
|
+
peripheral,
|
|
349
|
+
capability: "capture_output",
|
|
350
|
+
args: {
|
|
351
|
+
sessionId: args.sessionId,
|
|
352
|
+
},
|
|
353
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
354
|
+
processNote: parseProcessNote(value),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
function parseInteractiveSessionProposalPeripheral(value) {
|
|
360
|
+
if (value === INTERACTIVE_SESSION_PERIPHERAL || value === "workbench")
|
|
361
|
+
return INTERACTIVE_SESSION_PERIPHERAL;
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
function extractJsonObject(text) {
|
|
365
|
+
if (text.startsWith("{") && text.endsWith("}"))
|
|
366
|
+
return text;
|
|
367
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim();
|
|
368
|
+
if (fenced?.startsWith("{") && fenced.endsWith("}"))
|
|
369
|
+
return fenced;
|
|
370
|
+
const first = text.indexOf("{");
|
|
371
|
+
const last = text.lastIndexOf("}");
|
|
372
|
+
if (first >= 0 && last > first)
|
|
373
|
+
return text.slice(first, last + 1);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
async function narrateInteractiveSessionObservationWithCoreCm(input, proposal, observation) {
|
|
377
|
+
const memoryContext = await buildCoreCmMemoryContext({
|
|
378
|
+
agentName: input.agentName,
|
|
379
|
+
workdir: input.workdir || "",
|
|
380
|
+
});
|
|
381
|
+
const conversationContext = await buildCoreCmConversationContext({
|
|
382
|
+
agentName: input.agentName,
|
|
383
|
+
workdir: input.workdir || "",
|
|
384
|
+
conversationId: input.conversationId,
|
|
385
|
+
});
|
|
386
|
+
const context = [
|
|
387
|
+
"[Akemon Core CM Observation Review]",
|
|
388
|
+
`You are the Core Cognitive Module for ${input.agentName}.`,
|
|
389
|
+
"An interactive-session peripheral action has already been executed. The observation below is the source of truth.",
|
|
390
|
+
"",
|
|
391
|
+
"Architecture boundaries:",
|
|
392
|
+
"- Explain the action and observation to the owner in natural language.",
|
|
393
|
+
"- Do not expose JSON schemas, internal adapter names, or implementation details unless the owner asks.",
|
|
394
|
+
"- Memory CM participates here only as a proposal mechanism. Do not claim that memory has been written.",
|
|
395
|
+
"- Self memory belongs to Akemon. Work memory belongs to the owner/project and should only be proposed when useful.",
|
|
396
|
+
"- Use Akemon memory context to explain why the action matched the owner's project preferences when relevant.",
|
|
397
|
+
"",
|
|
398
|
+
formatOwnerLanguagePolicy(input.ownerLanguage),
|
|
399
|
+
"",
|
|
400
|
+
"Process visibility:",
|
|
401
|
+
"- Include processNote in the JSON response. This is the short CM-area narration for the owner.",
|
|
402
|
+
"- processNote must be natural language in the configured owner-facing language.",
|
|
403
|
+
"- processNote should explain how Core CM interpreted the interactive-session observation. Do not expose JSON keys, taskKind, adapter internals, token counts, or event ids unless the owner explicitly asks for debug details.",
|
|
404
|
+
"",
|
|
405
|
+
"Current interactive session context:",
|
|
406
|
+
describeInteractiveSessionsForCoreCm(input.interactiveSessions),
|
|
407
|
+
"",
|
|
408
|
+
"Akemon memory context:",
|
|
409
|
+
memoryContext,
|
|
410
|
+
"",
|
|
411
|
+
"Recent owner/Akemon chat context:",
|
|
412
|
+
conversationContext,
|
|
413
|
+
"",
|
|
414
|
+
input.workdir ? `Current workdir: ${input.workdir}` : "Current workdir: unknown",
|
|
415
|
+
].join("\n");
|
|
416
|
+
const question = [
|
|
417
|
+
"Owner message:",
|
|
418
|
+
input.text,
|
|
419
|
+
"",
|
|
420
|
+
"Interactive-session action requested:",
|
|
421
|
+
JSON.stringify(simplifyInteractiveSessionProposalForObservation(proposal), null, 2),
|
|
422
|
+
"",
|
|
423
|
+
"Peripheral observation:",
|
|
424
|
+
JSON.stringify(observation, null, 2),
|
|
425
|
+
"",
|
|
426
|
+
"Write the final response for the owner.",
|
|
427
|
+
"Write it as a short task report, not as a tool log.",
|
|
428
|
+
"Include what Akemon asked the peripheral to do, what actually happened, what output or lack of output was observed, and what this means for the owner's request.",
|
|
429
|
+
"If the action only sent input and no new output arrived yet, say that explicitly and suggest inspecting the session later if the owner wants the result.",
|
|
430
|
+
"Also include a concise Memory CM judgment in natural language. If nothing should be remembered, say that naturally.",
|
|
431
|
+
"",
|
|
432
|
+
"Return only JSON in this shape:",
|
|
433
|
+
"{",
|
|
434
|
+
" \"answer\": \"natural-language owner-facing response, without memory bookkeeping jargon\",",
|
|
435
|
+
" \"processNote\": \"one concise owner-facing sentence explaining how Core CM interpreted this observation\",",
|
|
436
|
+
" \"review\": {",
|
|
437
|
+
" \"resultQuality\": \"good | partial | failed | uncertain\",",
|
|
438
|
+
" \"completionDecision\": \"complete | partial | blocked | needs_follow_up\",",
|
|
439
|
+
" \"followUpNeeded\": false,",
|
|
440
|
+
" \"reportText\": \"concise final report text to archive\"",
|
|
441
|
+
" },",
|
|
442
|
+
" \"memory\": {",
|
|
443
|
+
" \"summary\": \"natural-language memory judgment\",",
|
|
444
|
+
" \"self\": [\"optional Akemon self-memory proposal\"],",
|
|
445
|
+
" \"work\": [\"optional owner/project work-memory proposal\"]",
|
|
446
|
+
" }",
|
|
447
|
+
"}",
|
|
448
|
+
].join("\n");
|
|
449
|
+
const result = await input.requestCompute({
|
|
450
|
+
context,
|
|
451
|
+
question,
|
|
452
|
+
taskKind: "core_observation_review",
|
|
453
|
+
purpose: "Core CM reviews a Workbench peripheral observation and writes the owner-facing response.",
|
|
454
|
+
priority: "normal",
|
|
455
|
+
origin: "user_manual",
|
|
456
|
+
engineHints: {
|
|
457
|
+
requiredCapabilities: ["chat", "json"],
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
if (!result.success) {
|
|
461
|
+
const output = formatInteractiveSessionObservationFallback(input.text, proposal, observation, input.ownerLanguage);
|
|
462
|
+
return {
|
|
463
|
+
output,
|
|
464
|
+
review: createCoreCmFallbackReviewDraft({ observation, output }),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const response = parseCoreCmObservationResponse(result.response);
|
|
468
|
+
if (!response) {
|
|
469
|
+
const output = formatInteractiveSessionObservationFallback(input.text, proposal, observation, input.ownerLanguage);
|
|
470
|
+
return {
|
|
471
|
+
output,
|
|
472
|
+
review: createCoreCmFallbackReviewDraft({ observation, output }),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const memoryProposal = createMemoryProposal({
|
|
476
|
+
agentName: input.agentName,
|
|
477
|
+
workdir: input.workdir || "",
|
|
478
|
+
source: "core-cm-observation-review",
|
|
479
|
+
content: response.memory,
|
|
480
|
+
reason: proposal.reason,
|
|
481
|
+
}) || undefined;
|
|
482
|
+
const output = formatCoreCmObservationResponse(response);
|
|
483
|
+
return {
|
|
484
|
+
output,
|
|
485
|
+
memoryProposal,
|
|
486
|
+
processNotes: collectProcessNotes(response.processNote),
|
|
487
|
+
review: createCoreCmObservationReviewDraft({ response, observation, output }),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function parseProcessNote(value) {
|
|
491
|
+
return typeof value.processNote === "string" && value.processNote.trim()
|
|
492
|
+
? value.processNote.trim()
|
|
493
|
+
: undefined;
|
|
494
|
+
}
|
|
495
|
+
function collectProcessNotes(...notes) {
|
|
496
|
+
const result = [];
|
|
497
|
+
const seen = new Set();
|
|
498
|
+
for (const note of notes) {
|
|
499
|
+
const text = typeof note === "string" ? note.trim() : "";
|
|
500
|
+
if (!text || seen.has(text))
|
|
501
|
+
continue;
|
|
502
|
+
seen.add(text);
|
|
503
|
+
result.push(text);
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
function formatOwnerLanguagePolicy(ownerLanguage) {
|
|
508
|
+
const normalized = typeof ownerLanguage === "string" && ownerLanguage.trim()
|
|
509
|
+
? ownerLanguage.trim()
|
|
510
|
+
: "auto";
|
|
511
|
+
if (normalized.toLowerCase() === "auto") {
|
|
512
|
+
return [
|
|
513
|
+
"Owner-facing language policy:",
|
|
514
|
+
"- Mode: auto.",
|
|
515
|
+
"- Respond in the owner's current message language when it is clear.",
|
|
516
|
+
"- If the message language is ambiguous, use the dominant language in recent owner/Akemon chat context and relevant project memory.",
|
|
517
|
+
"- Keep code, commands, file paths, identifiers, JSON keys, and tool names unchanged.",
|
|
518
|
+
].join("\n");
|
|
519
|
+
}
|
|
520
|
+
return [
|
|
521
|
+
"Owner-facing language policy:",
|
|
522
|
+
`- Preferred owner-facing language: ${normalized}.`,
|
|
523
|
+
"- Use this language for natural-language replies, memory judgments, explanations, and user-facing summaries unless the owner explicitly asks for another language in the current message.",
|
|
524
|
+
"- Keep code, commands, file paths, identifiers, JSON keys, and tool names unchanged.",
|
|
525
|
+
].join("\n");
|
|
526
|
+
}
|
|
527
|
+
function simplifyInteractiveSessionProposalForObservation(proposal) {
|
|
528
|
+
return {
|
|
529
|
+
peripheral: proposal.peripheral,
|
|
530
|
+
capability: proposal.capability,
|
|
531
|
+
args: proposal.args || {},
|
|
532
|
+
reason: proposal.reason,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function parseCoreCmObservationResponse(raw) {
|
|
536
|
+
const text = raw?.trim();
|
|
537
|
+
if (!text)
|
|
538
|
+
return null;
|
|
539
|
+
const jsonText = extractJsonObject(text);
|
|
540
|
+
if (!jsonText)
|
|
541
|
+
return null;
|
|
542
|
+
let parsed;
|
|
543
|
+
try {
|
|
544
|
+
parsed = JSON.parse(jsonText);
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
if (!parsed || typeof parsed !== "object")
|
|
550
|
+
return null;
|
|
551
|
+
const value = parsed;
|
|
552
|
+
if (typeof value.answer !== "string")
|
|
553
|
+
return null;
|
|
554
|
+
const memory = value.memory && typeof value.memory === "object"
|
|
555
|
+
? value.memory
|
|
556
|
+
: undefined;
|
|
557
|
+
return {
|
|
558
|
+
answer: value.answer,
|
|
559
|
+
processNote: parseProcessNote(value),
|
|
560
|
+
review: parseCoreCmObservationReview(value.review),
|
|
561
|
+
...(memory ? {
|
|
562
|
+
memory: {
|
|
563
|
+
summary: typeof memory.summary === "string" ? memory.summary : undefined,
|
|
564
|
+
self: Array.isArray(memory.self) ? normalizeStringArray(memory.self) : undefined,
|
|
565
|
+
work: Array.isArray(memory.work) ? normalizeStringArray(memory.work) : undefined,
|
|
566
|
+
},
|
|
567
|
+
} : {}),
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function parseCoreCmObservationReview(value) {
|
|
571
|
+
if (!value || typeof value !== "object")
|
|
572
|
+
return undefined;
|
|
573
|
+
const record = value;
|
|
574
|
+
const resultQuality = normalizeCoreCmResultQuality(record.resultQuality);
|
|
575
|
+
const completionDecision = normalizeCoreCmCompletionDecision(record.completionDecision);
|
|
576
|
+
const followUpNeeded = typeof record.followUpNeeded === "boolean" ? record.followUpNeeded : undefined;
|
|
577
|
+
const reportText = typeof record.reportText === "string" && record.reportText.trim()
|
|
578
|
+
? record.reportText.trim()
|
|
579
|
+
: undefined;
|
|
580
|
+
const summary = typeof record.summary === "string" && record.summary.trim()
|
|
581
|
+
? record.summary.trim()
|
|
582
|
+
: undefined;
|
|
583
|
+
if (!resultQuality && !completionDecision && followUpNeeded === undefined && !reportText && !summary)
|
|
584
|
+
return undefined;
|
|
585
|
+
return {
|
|
586
|
+
...(resultQuality ? { resultQuality } : {}),
|
|
587
|
+
...(completionDecision ? { completionDecision } : {}),
|
|
588
|
+
...(followUpNeeded !== undefined ? { followUpNeeded } : {}),
|
|
589
|
+
...(reportText ? { reportText } : {}),
|
|
590
|
+
...(summary ? { summary } : {}),
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function createCoreCmObservationReviewDraft(input) {
|
|
594
|
+
const completionDecision = input.response.review?.completionDecision
|
|
595
|
+
|| deriveCoreCmCompletionDecision(input.observation);
|
|
596
|
+
const resultQuality = input.response.review?.resultQuality
|
|
597
|
+
|| deriveCoreCmResultQuality(input.observation, completionDecision);
|
|
598
|
+
const followUpNeeded = typeof input.response.review?.followUpNeeded === "boolean"
|
|
599
|
+
? input.response.review.followUpNeeded
|
|
600
|
+
: completionDecision !== "complete";
|
|
601
|
+
return {
|
|
602
|
+
resultQuality,
|
|
603
|
+
completionDecision,
|
|
604
|
+
followUpNeeded,
|
|
605
|
+
reportText: truncateReviewText(input.response.review?.reportText || input.output, 4000),
|
|
606
|
+
summary: input.response.review?.summary || input.response.processNote || summarizeCoreCmReviewDecision(resultQuality, completionDecision),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function createCoreCmFallbackReviewDraft(input) {
|
|
610
|
+
const completionDecision = deriveCoreCmCompletionDecision(input.observation);
|
|
611
|
+
const resultQuality = deriveCoreCmResultQuality(input.observation, completionDecision);
|
|
612
|
+
return {
|
|
613
|
+
resultQuality,
|
|
614
|
+
completionDecision,
|
|
615
|
+
followUpNeeded: completionDecision !== "complete",
|
|
616
|
+
reportText: truncateReviewText(input.output, 4000),
|
|
617
|
+
summary: summarizeCoreCmReviewDecision(resultQuality, completionDecision),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function normalizeCoreCmResultQuality(value) {
|
|
621
|
+
return value === "good" || value === "partial" || value === "failed" || value === "uncertain"
|
|
622
|
+
? value
|
|
623
|
+
: undefined;
|
|
624
|
+
}
|
|
625
|
+
function normalizeCoreCmCompletionDecision(value) {
|
|
626
|
+
return value === "complete" || value === "partial" || value === "blocked" || value === "needs_follow_up"
|
|
627
|
+
? value
|
|
628
|
+
: undefined;
|
|
629
|
+
}
|
|
630
|
+
function deriveCoreCmCompletionDecision(observation) {
|
|
631
|
+
if (!observation.ok)
|
|
632
|
+
return "blocked";
|
|
633
|
+
if (observation.capability === "send_input") {
|
|
634
|
+
const newOutput = typeof observation.data?.newOutput === "string"
|
|
635
|
+
? observation.data.newOutput.trim()
|
|
636
|
+
: "";
|
|
637
|
+
return newOutput ? "complete" : "needs_follow_up";
|
|
638
|
+
}
|
|
639
|
+
return "complete";
|
|
640
|
+
}
|
|
641
|
+
function deriveCoreCmResultQuality(observation, completionDecision) {
|
|
642
|
+
if (!observation.ok || completionDecision === "blocked")
|
|
643
|
+
return "failed";
|
|
644
|
+
if (completionDecision === "partial" || completionDecision === "needs_follow_up")
|
|
645
|
+
return "partial";
|
|
646
|
+
return "good";
|
|
647
|
+
}
|
|
648
|
+
function summarizeCoreCmReviewDecision(resultQuality, completionDecision) {
|
|
649
|
+
return `Core CM review recorded ${completionDecision} completion with ${resultQuality} result quality.`;
|
|
650
|
+
}
|
|
651
|
+
function truncateReviewText(value, maxChars) {
|
|
652
|
+
const text = value.trim();
|
|
653
|
+
if (text.length <= maxChars)
|
|
654
|
+
return text;
|
|
655
|
+
return `${text.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
656
|
+
}
|
|
657
|
+
function formatCoreCmObservationResponse(response) {
|
|
658
|
+
const answer = response.answer.trim();
|
|
659
|
+
const memoryText = formatMemoryProposal(response.memory);
|
|
660
|
+
return [
|
|
661
|
+
answer || "I do not have a useful answer yet.",
|
|
662
|
+
memoryText,
|
|
663
|
+
].filter(Boolean).join("\n\n");
|
|
664
|
+
}
|
|
665
|
+
function formatInteractiveSessionObservationFallback(ownerText, proposal, observation, ownerLanguage) {
|
|
666
|
+
const chinese = shouldUseChineseOwnerText(ownerLanguage, ownerText);
|
|
667
|
+
const lines = [observation.summary];
|
|
668
|
+
if (!observation.ok) {
|
|
669
|
+
lines.push("", chinese
|
|
670
|
+
? "这次外设调用没有完成。我没有写入记忆。"
|
|
671
|
+
: "This peripheral action did not complete. I did not write any memory.");
|
|
672
|
+
return lines.join("\n");
|
|
673
|
+
}
|
|
674
|
+
const newOutput = typeof observation.data?.newOutput === "string"
|
|
675
|
+
? observation.data.newOutput.trim()
|
|
676
|
+
: "";
|
|
677
|
+
if (proposal.capability === "send_input" && !newOutput) {
|
|
678
|
+
lines.push("", chinese
|
|
679
|
+
? "我还没有看到这个会话返回新输出。需要的话,你可以让我稍后再检查这个会话。"
|
|
680
|
+
: "I have not seen new output from this session yet. You can ask me to inspect it again later if you need the result.");
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
lines.push("", chinese
|
|
684
|
+
? "这次结果不需要写入长期记忆。"
|
|
685
|
+
: "No long-term memory change is needed for this result.");
|
|
686
|
+
}
|
|
687
|
+
return lines.join("\n");
|
|
688
|
+
}
|
|
689
|
+
function shouldUseChineseOwnerText(ownerLanguage, ownerText) {
|
|
690
|
+
const normalized = typeof ownerLanguage === "string" && ownerLanguage.trim()
|
|
691
|
+
? ownerLanguage.trim().toLowerCase()
|
|
692
|
+
: "auto";
|
|
693
|
+
if (normalized === "auto")
|
|
694
|
+
return /[\u3400-\u9fff]/.test(ownerText || "");
|
|
695
|
+
return normalized === "zh"
|
|
696
|
+
|| normalized === "zh-cn"
|
|
697
|
+
|| normalized === "zh_cn"
|
|
698
|
+
|| normalized === "chinese"
|
|
699
|
+
|| normalized === "simplified chinese"
|
|
700
|
+
|| normalized === "中文"
|
|
701
|
+
|| normalized === "简体中文";
|
|
702
|
+
}
|
|
703
|
+
function formatMemoryProposal(memory) {
|
|
704
|
+
if (!memory)
|
|
705
|
+
return "";
|
|
706
|
+
const lines = [];
|
|
707
|
+
if (memory.summary?.trim())
|
|
708
|
+
lines.push(memory.summary.trim());
|
|
709
|
+
return lines.join("\n");
|
|
710
|
+
}
|
|
711
|
+
async function buildCoreCmConversationContext(input) {
|
|
712
|
+
if (!input.conversationId)
|
|
713
|
+
return "- No conversation id was provided for this chat.";
|
|
714
|
+
try {
|
|
715
|
+
const conversation = await loadConversation(input.workdir, input.agentName, input.conversationId);
|
|
716
|
+
const context = buildLLMContext(conversation, 4_000).text.trim();
|
|
717
|
+
return context || "- No prior turns in this conversation.";
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
return `- Conversation context could not be loaded: ${error?.message || String(error)}`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async function buildCoreCmMemoryContext(input) {
|
|
724
|
+
try {
|
|
725
|
+
return await formatCurrentMemoryForCoreCm({
|
|
726
|
+
agentName: input.agentName,
|
|
727
|
+
workdir: input.workdir,
|
|
728
|
+
scope: "all",
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
return [
|
|
733
|
+
"Akemon memory could not be loaded for this chat turn.",
|
|
734
|
+
`Reason: ${error?.message || String(error)}`,
|
|
735
|
+
"",
|
|
736
|
+
"Pending memory proposals:",
|
|
737
|
+
describePendingMemoryProposalsForCoreCm(input),
|
|
738
|
+
].join("\n");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function describeInteractiveSessionsForCoreCm(interactiveSessions) {
|
|
742
|
+
if (!interactiveSessions) {
|
|
743
|
+
return "- Interactive session adapter is not available in this runtime.";
|
|
744
|
+
}
|
|
745
|
+
const sessions = interactiveSessions.listSessions();
|
|
746
|
+
if (!sessions.length) {
|
|
747
|
+
return "- No interactive sessions are currently active or recently tracked.";
|
|
748
|
+
}
|
|
749
|
+
return sessions.map((session) => {
|
|
750
|
+
return [
|
|
751
|
+
`- ${session.sessionId}`,
|
|
752
|
+
` tool: ${session.tool}`,
|
|
753
|
+
` status: ${session.status}`,
|
|
754
|
+
` command: ${session.commandLineDisplay}`,
|
|
755
|
+
` workdir: ${session.workdir}`,
|
|
756
|
+
` size: ${session.cols}x${session.rows}`,
|
|
757
|
+
` log: ${session.logPath}`,
|
|
758
|
+
].join("\n");
|
|
759
|
+
}).join("\n");
|
|
760
|
+
}
|
|
761
|
+
function normalizeStringArray(value) {
|
|
762
|
+
return value.map((item) => String(item));
|
|
763
|
+
}
|
|
764
|
+
function normalizeSendInputWaitMs(value) {
|
|
765
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
766
|
+
return DEFAULT_SEND_INPUT_WAIT_MS;
|
|
767
|
+
return Math.max(0, Math.min(Math.round(value), MAX_SEND_INPUT_WAIT_MS));
|
|
768
|
+
}
|