akemon 0.2.20 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/role-module.js +286 -0
- package/dist/self.js +9 -0
- package/dist/server.js +8 -1
- package/dist/task-module.js +8 -4
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoleModule — agent identity and expertise system.
|
|
3
|
+
*
|
|
4
|
+
* Role = who you are (tone, privacy boundary)
|
|
5
|
+
* Playbook = what you know (domain strategy, toolchain)
|
|
6
|
+
* Product = what you sell (specific offering, references a playbook)
|
|
7
|
+
*
|
|
8
|
+
* All three are orthogonal and decoupled.
|
|
9
|
+
* Core functions are exported for TaskModule to call directly (pure-function style).
|
|
10
|
+
*/
|
|
11
|
+
import { readdir, readFile, mkdir, writeFile } from "fs/promises";
|
|
12
|
+
import { join, basename } from "path";
|
|
13
|
+
import { rolesDir, playbooksDir, productsDir } from "./self.js";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Parsing
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
function parseRole(name, raw) {
|
|
18
|
+
const lines = raw.split("\n");
|
|
19
|
+
let description = "";
|
|
20
|
+
const triggers = [];
|
|
21
|
+
const include = [];
|
|
22
|
+
const exclude = [];
|
|
23
|
+
const customLines = [];
|
|
24
|
+
let section = ""; // current ## section name
|
|
25
|
+
let pastTitle = false;
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
// Track ## sections
|
|
28
|
+
if (line.startsWith("## ")) {
|
|
29
|
+
const heading = line.slice(3).trim().toLowerCase();
|
|
30
|
+
if (heading.includes("激活") || heading === "triggers") {
|
|
31
|
+
section = "triggers";
|
|
32
|
+
}
|
|
33
|
+
else if (heading.includes("上下文") || heading.includes("context")) {
|
|
34
|
+
section = "context";
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
section = "custom";
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Skip # title
|
|
42
|
+
if (line.startsWith("# ")) {
|
|
43
|
+
pastTitle = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Extract description: first non-empty line after title, before any ##
|
|
47
|
+
if (pastTitle && !description && !section && line.trim()) {
|
|
48
|
+
description = line.trim();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
if (!trimmed)
|
|
53
|
+
continue;
|
|
54
|
+
if (section === "triggers") {
|
|
55
|
+
// Extract trigger:xxx from list items
|
|
56
|
+
const match = trimmed.match(/^-\s*(trigger:\S+)/);
|
|
57
|
+
if (match)
|
|
58
|
+
triggers.push(match[1]);
|
|
59
|
+
}
|
|
60
|
+
else if (section === "context") {
|
|
61
|
+
// Parse include/exclude lines
|
|
62
|
+
if (trimmed.toLowerCase().startsWith("include:")) {
|
|
63
|
+
include.push(...trimmed.slice(8).split(",").map(s => s.trim()).filter(Boolean));
|
|
64
|
+
}
|
|
65
|
+
else if (trimmed.toLowerCase().startsWith("exclude:")) {
|
|
66
|
+
exclude.push(...trimmed.slice(8).split(",").map(s => s.trim()).filter(Boolean));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (section === "custom") {
|
|
70
|
+
customLines.push(line);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { name, description, triggers, include, exclude, customRules: customLines.join("\n").trim(), raw };
|
|
74
|
+
}
|
|
75
|
+
function parseProduct(name, raw) {
|
|
76
|
+
let playbook = "";
|
|
77
|
+
const lines = raw.split("\n");
|
|
78
|
+
let inPlaybook = false;
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (line.startsWith("## ") && line.toLowerCase().includes("playbook")) {
|
|
81
|
+
inPlaybook = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (line.startsWith("## ")) {
|
|
85
|
+
inPlaybook = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (inPlaybook && line.trim()) {
|
|
89
|
+
playbook = line.trim();
|
|
90
|
+
inPlaybook = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { name, playbook, raw };
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Loading (pure functions — called by TaskModule directly)
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
async function loadMdFiles(dir) {
|
|
99
|
+
try {
|
|
100
|
+
const files = await readdir(dir);
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const f of files) {
|
|
103
|
+
if (!f.endsWith(".md"))
|
|
104
|
+
continue;
|
|
105
|
+
const raw = await readFile(join(dir, f), "utf-8");
|
|
106
|
+
results.push({ name: basename(f, ".md"), raw });
|
|
107
|
+
}
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return []; // directory doesn't exist or empty
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function loadRoles(workdir, agentName) {
|
|
115
|
+
const entries = await loadMdFiles(rolesDir(workdir, agentName));
|
|
116
|
+
return entries.map(e => parseRole(e.name, e.raw));
|
|
117
|
+
}
|
|
118
|
+
export async function loadPlaybooks(workdir, agentName) {
|
|
119
|
+
const entries = await loadMdFiles(playbooksDir(workdir, agentName));
|
|
120
|
+
return entries.map(e => ({ name: e.name, raw: e.raw }));
|
|
121
|
+
}
|
|
122
|
+
export async function loadProducts(workdir, agentName) {
|
|
123
|
+
const entries = await loadMdFiles(productsDir(workdir, agentName));
|
|
124
|
+
return entries.map(e => parseProduct(e.name, e.raw));
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Resolution
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
export function resolveRoles(roles, trigger) {
|
|
130
|
+
const matched = roles.filter(r => r.triggers.some(t => trigger.includes(t) || t.includes(trigger)));
|
|
131
|
+
if (matched.length === 0)
|
|
132
|
+
return { primary: null, secondary: [] };
|
|
133
|
+
return { primary: matched[0], secondary: matched.slice(1) };
|
|
134
|
+
}
|
|
135
|
+
export function resolveProduct(products, playbooks, productName) {
|
|
136
|
+
if (!productName)
|
|
137
|
+
return null;
|
|
138
|
+
const normalized = productName.toLowerCase().replace(/[\s_-]+/g, "");
|
|
139
|
+
const product = products.find(p => {
|
|
140
|
+
const pNorm = p.name.toLowerCase().replace(/[\s_-]+/g, "");
|
|
141
|
+
return pNorm === normalized || normalized.includes(pNorm) || pNorm.includes(normalized);
|
|
142
|
+
});
|
|
143
|
+
if (!product)
|
|
144
|
+
return null;
|
|
145
|
+
const playbook = product.playbook
|
|
146
|
+
? playbooks.find(pb => pb.name.toLowerCase() === product.playbook.toLowerCase()) ?? null
|
|
147
|
+
: null;
|
|
148
|
+
return { product, playbook };
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Context building (main entry point for TaskModule)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
export async function buildRoleContext(workdir, agentName, trigger, productName) {
|
|
154
|
+
const roles = await loadRoles(workdir, agentName);
|
|
155
|
+
const playbooks = await loadPlaybooks(workdir, agentName);
|
|
156
|
+
const products = await loadProducts(workdir, agentName);
|
|
157
|
+
const { primary, secondary } = resolveRoles(roles, trigger);
|
|
158
|
+
if (primary) {
|
|
159
|
+
console.log(`[role] trigger=${trigger} → primary=${primary.name}${secondary.length > 0 ? ` secondary=${secondary.map(r => r.name).join(",")}` : ""}`);
|
|
160
|
+
}
|
|
161
|
+
const parts = [];
|
|
162
|
+
// Primary role: full content
|
|
163
|
+
if (primary) {
|
|
164
|
+
parts.push(`[Active role: ${primary.name}]\n${primary.raw}`);
|
|
165
|
+
}
|
|
166
|
+
// Secondary roles: description only
|
|
167
|
+
for (const r of secondary) {
|
|
168
|
+
if (r.description) {
|
|
169
|
+
parts.push(`[Secondary role: ${r.name}] ${r.description}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Product + playbook
|
|
173
|
+
if (productName) {
|
|
174
|
+
const resolved = resolveProduct(products, playbooks, productName);
|
|
175
|
+
if (resolved) {
|
|
176
|
+
parts.push(`[Product: ${resolved.product.name}]\n${resolved.product.raw}`);
|
|
177
|
+
if (resolved.playbook) {
|
|
178
|
+
parts.push(`[Playbook: ${resolved.playbook.name}]\n${resolved.playbook.raw}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// If no product matched but playbooks exist, list them for reference
|
|
183
|
+
if (!productName && playbooks.length > 0) {
|
|
184
|
+
parts.push(`Available playbooks: ${playbooks.map(p => p.name).join(", ")}`);
|
|
185
|
+
}
|
|
186
|
+
return parts.join("\n\n");
|
|
187
|
+
}
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Default templates
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
const DEFAULT_ROLES = {
|
|
192
|
+
merchant: `# 商家
|
|
193
|
+
|
|
194
|
+
你作为商家为客户服务,行为和表达要符合职业规范,不断提高服务水平。
|
|
195
|
+
|
|
196
|
+
## 激活
|
|
197
|
+
- trigger:order
|
|
198
|
+
- trigger:chat:public
|
|
199
|
+
|
|
200
|
+
## 上下文范围
|
|
201
|
+
include: buyer 历史订单, 商品信息
|
|
202
|
+
exclude: owner 对话, 个人笔记, bio 状态
|
|
203
|
+
`,
|
|
204
|
+
companion: `# 陪伴者
|
|
205
|
+
|
|
206
|
+
你是 owner 的伙伴和助手,了解 owner 的偏好和习惯,提供贴心的支持。
|
|
207
|
+
|
|
208
|
+
## 激活
|
|
209
|
+
- trigger:chat:owner
|
|
210
|
+
- trigger:user_task
|
|
211
|
+
|
|
212
|
+
## 上下文范围
|
|
213
|
+
include: owner 对话历史, 个人笔记, bio 状态, 全部记忆
|
|
214
|
+
exclude: 其他 buyer 的对话和订单
|
|
215
|
+
`,
|
|
216
|
+
worker: `# 打工人
|
|
217
|
+
|
|
218
|
+
你按照要求完成任务,高效、准确、不多废话。
|
|
219
|
+
|
|
220
|
+
## 激活
|
|
221
|
+
- trigger:agent_call
|
|
222
|
+
|
|
223
|
+
## 上下文范围
|
|
224
|
+
include: 任务相关上下文
|
|
225
|
+
exclude: owner 私人对话, buyer 信息
|
|
226
|
+
`,
|
|
227
|
+
};
|
|
228
|
+
async function ensureDefaultRoles(workdir, agentName) {
|
|
229
|
+
const dir = rolesDir(workdir, agentName);
|
|
230
|
+
try {
|
|
231
|
+
await readdir(dir);
|
|
232
|
+
// Directory exists — respect user's choices, don't create defaults
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Directory doesn't exist — create with defaults
|
|
237
|
+
}
|
|
238
|
+
await mkdir(dir, { recursive: true });
|
|
239
|
+
for (const [name, content] of Object.entries(DEFAULT_ROLES)) {
|
|
240
|
+
await writeFile(join(dir, `${name}.md`), content, "utf-8");
|
|
241
|
+
}
|
|
242
|
+
console.log(`[role] Created default role templates in ${dir}`);
|
|
243
|
+
}
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// RoleModule class (Module interface)
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
export class RoleModule {
|
|
248
|
+
id = "role";
|
|
249
|
+
name = "Role & Playbook System";
|
|
250
|
+
dependencies = ["memory"];
|
|
251
|
+
ctx = null;
|
|
252
|
+
currentPrimary = null;
|
|
253
|
+
async start(ctx) {
|
|
254
|
+
this.ctx = ctx;
|
|
255
|
+
const { workdir, agentName } = ctx;
|
|
256
|
+
// Create default templates if roles/ doesn't exist
|
|
257
|
+
await ensureDefaultRoles(workdir, agentName);
|
|
258
|
+
// Also ensure playbooks/ and products/ directories exist
|
|
259
|
+
await mkdir(playbooksDir(workdir, agentName), { recursive: true });
|
|
260
|
+
await mkdir(productsDir(workdir, agentName), { recursive: true });
|
|
261
|
+
// Load and log
|
|
262
|
+
const roles = await loadRoles(workdir, agentName);
|
|
263
|
+
const playbooks = await loadPlaybooks(workdir, agentName);
|
|
264
|
+
const products = await loadProducts(workdir, agentName);
|
|
265
|
+
console.log(`[role] Loaded ${roles.length} roles, ${playbooks.length} playbooks, ${products.length} products`);
|
|
266
|
+
}
|
|
267
|
+
async stop() {
|
|
268
|
+
this.currentPrimary = null;
|
|
269
|
+
this.ctx = null;
|
|
270
|
+
}
|
|
271
|
+
/** Current active role summary — lets other modules (Memory, Reflection) sense the role */
|
|
272
|
+
promptContribution() {
|
|
273
|
+
if (!this.currentPrimary)
|
|
274
|
+
return null;
|
|
275
|
+
return `Current role: ${this.currentPrimary.name} — ${this.currentPrimary.description}`;
|
|
276
|
+
}
|
|
277
|
+
getState() {
|
|
278
|
+
return {
|
|
279
|
+
currentRole: this.currentPrimary?.name ?? null,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/** Called by buildRoleContext to update current role state */
|
|
283
|
+
updateCurrentRole(role) {
|
|
284
|
+
this.currentPrimary = role;
|
|
285
|
+
}
|
|
286
|
+
}
|
package/dist/self.js
CHANGED
|
@@ -45,6 +45,15 @@ export function notesDir(workdir, agentName) {
|
|
|
45
45
|
export function pagesDir(workdir, agentName) {
|
|
46
46
|
return join(selfDir(workdir, agentName), "pages");
|
|
47
47
|
}
|
|
48
|
+
export function rolesDir(workdir, agentName) {
|
|
49
|
+
return join(selfDir(workdir, agentName), "roles");
|
|
50
|
+
}
|
|
51
|
+
export function playbooksDir(workdir, agentName) {
|
|
52
|
+
return join(selfDir(workdir, agentName), "playbooks");
|
|
53
|
+
}
|
|
54
|
+
export function productsDir(workdir, agentName) {
|
|
55
|
+
return join(selfDir(workdir, agentName), "products");
|
|
56
|
+
}
|
|
48
57
|
export function guidePath(workdir, agentName) {
|
|
49
58
|
return join(selfDir(workdir, agentName), "guide.md");
|
|
50
59
|
}
|
package/dist/server.js
CHANGED
|
@@ -76,6 +76,7 @@ import { RelayPeripheral } from "./relay-peripheral.js";
|
|
|
76
76
|
import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
|
|
77
77
|
import { BioStateModule } from "./bio-module.js";
|
|
78
78
|
import { MemoryModule } from "./memory-module.js";
|
|
79
|
+
import { RoleModule } from "./role-module.js";
|
|
79
80
|
import { TaskModule } from "./task-module.js";
|
|
80
81
|
import { SocialModule } from "./social-module.js";
|
|
81
82
|
import { LongTermModule } from "./longterm-module.js";
|
|
@@ -401,7 +402,7 @@ export async function serve(options) {
|
|
|
401
402
|
},
|
|
402
403
|
};
|
|
403
404
|
// V2: Conditionally load modules based on --with/--without
|
|
404
|
-
const enabled = options.enabledModules ?? ["biostate", "memory", "task", "social", "longterm", "reflection", "script"];
|
|
405
|
+
const enabled = options.enabledModules ?? ["biostate", "memory", "role", "task", "social", "longterm", "reflection", "script"];
|
|
405
406
|
const loadedModules = [];
|
|
406
407
|
const allModules = [];
|
|
407
408
|
if (enabled.includes("biostate")) {
|
|
@@ -420,6 +421,12 @@ export async function serve(options) {
|
|
|
420
421
|
allModules.push(memoryModule);
|
|
421
422
|
loadedModules.push("memory");
|
|
422
423
|
}
|
|
424
|
+
if (enabled.includes("role")) {
|
|
425
|
+
const roleModule = new RoleModule();
|
|
426
|
+
await roleModule.start(moduleCtx);
|
|
427
|
+
allModules.push(roleModule);
|
|
428
|
+
loadedModules.push("role");
|
|
429
|
+
}
|
|
423
430
|
if (enabled.includes("task")) {
|
|
424
431
|
const taskModule = new TaskModule();
|
|
425
432
|
taskModule.relayHttp = options.relayHttp || "";
|
package/dist/task-module.js
CHANGED
|
@@ -12,6 +12,7 @@ import { readFile } from "fs/promises";
|
|
|
12
12
|
import { SIG, sig } from "./types.js";
|
|
13
13
|
import { selfDir, biosPath, localNow, loadBioState, saveBioState, syncEnergyFromTokens, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, loadDirectives, buildDirectivesPrompt, appendTaskHistory, notifyOwner, updateHungerDecay, updateNaturalDecay, resetTokenCountIfNewDay, computeSociability, appendBioEvent, bioStatePromptModifier, feedHunger, SHOP_ITEMS, logBioStatus, logBioDecision, } from "./self.js";
|
|
14
14
|
import { appendMessage, resolveConvId } from "./context.js";
|
|
15
|
+
import { buildRoleContext } from "./role-module.js";
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Config
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -308,9 +309,10 @@ export class TaskModule {
|
|
|
308
309
|
}
|
|
309
310
|
catch { }
|
|
310
311
|
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
311
|
-
|
|
312
|
+
const roleBlock = await buildRoleContext(workdir, agentName, "order", order.product_name);
|
|
313
|
+
// Build context: agent identity + role + order details + relay API reference
|
|
312
314
|
const context = `You are ${agentName}.${bioMod}
|
|
313
|
-
|
|
315
|
+
${roleBlock ? `\n${roleBlock}\n` : ""}
|
|
314
316
|
Your operating document:
|
|
315
317
|
---
|
|
316
318
|
${biosContent.slice(0, 3000)}
|
|
@@ -436,8 +438,9 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
|
|
|
436
438
|
}
|
|
437
439
|
catch { }
|
|
438
440
|
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
441
|
+
const roleBlock = await buildRoleContext(workdir, agentName, "user_task");
|
|
439
442
|
const context = `You are ${agentName}.${bioMod}
|
|
440
|
-
|
|
443
|
+
${roleBlock ? `\n${roleBlock}\n` : ""}
|
|
441
444
|
Your operating document:
|
|
442
445
|
---
|
|
443
446
|
${biosContent.slice(0, 3000)}
|
|
@@ -522,6 +525,7 @@ Your personal directory: ${sd}/`;
|
|
|
522
525
|
const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
|
|
523
526
|
const dirs = await loadDirectives(workdir, agentName);
|
|
524
527
|
const dirsBlock = buildDirectivesPrompt(dirs, "owner");
|
|
528
|
+
const roleBlock = await buildRoleContext(workdir, agentName, "agent_call");
|
|
525
529
|
// Explore to get environment context (products, market, etc.)
|
|
526
530
|
let envBriefing = "";
|
|
527
531
|
try {
|
|
@@ -529,7 +533,7 @@ Your personal directory: ${sd}/`;
|
|
|
529
533
|
}
|
|
530
534
|
catch { }
|
|
531
535
|
const context = `You are ${agentName}.${bioMod}
|
|
532
|
-
|
|
536
|
+
${roleBlock ? `\n${roleBlock}\n` : ""}
|
|
533
537
|
Your operating document:
|
|
534
538
|
---
|
|
535
539
|
${biosContent.slice(0, 3000)}
|