akemon 0.3.6 → 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 +11 -3
- package/README.md +133 -21
- 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 +1188 -100
- 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 +3675 -512
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +185 -244
- 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 +59 -15
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +8 -3
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { redactSecrets } from "./redaction.js";
|
|
5
|
+
const ARTIFACT_FILE_BY_KIND = {
|
|
6
|
+
reflection: "reflections.jsonl",
|
|
7
|
+
landmark_candidate: "landmark-candidates.jsonl",
|
|
8
|
+
memory_weight: "memory-weights.jsonl",
|
|
9
|
+
memory_relation: "memory-relations.jsonl",
|
|
10
|
+
failed_attempt: "failed-attempts.jsonl",
|
|
11
|
+
rejected_approach: "rejected-approaches.jsonl",
|
|
12
|
+
};
|
|
13
|
+
export function createCognitiveArtifactId(kind) {
|
|
14
|
+
return `${kind}_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
export function cognitiveArtifactsDir(workdir, agentName) {
|
|
17
|
+
return join(workdir, ".akemon", "agents", agentName, "cognitive");
|
|
18
|
+
}
|
|
19
|
+
export function cognitiveArtifactPath(workdir, agentName, kind) {
|
|
20
|
+
return join(cognitiveArtifactsDir(workdir, agentName), ARTIFACT_FILE_BY_KIND[kind]);
|
|
21
|
+
}
|
|
22
|
+
export async function appendCognitiveArtifact(input) {
|
|
23
|
+
const now = new Date().toISOString();
|
|
24
|
+
const kind = input.record.kind;
|
|
25
|
+
const record = redactSecrets({
|
|
26
|
+
...input.record,
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
id: input.record.id || createCognitiveArtifactId(kind),
|
|
29
|
+
createdAt: input.record.createdAt || now,
|
|
30
|
+
updatedAt: input.record.updatedAt || input.record.createdAt || now,
|
|
31
|
+
agentName: input.agentName,
|
|
32
|
+
workdir: input.workdir,
|
|
33
|
+
refs: input.record.refs || [],
|
|
34
|
+
tags: normalizeTags(input.record.tags),
|
|
35
|
+
});
|
|
36
|
+
await mkdir(cognitiveArtifactsDir(input.workdir, input.agentName), { recursive: true });
|
|
37
|
+
await appendFile(cognitiveArtifactPath(input.workdir, input.agentName, kind), `${JSON.stringify(record)}\n`, "utf-8");
|
|
38
|
+
return record;
|
|
39
|
+
}
|
|
40
|
+
export async function recordFailedAttempt(input) {
|
|
41
|
+
return appendCognitiveArtifact({
|
|
42
|
+
workdir: input.workdir,
|
|
43
|
+
agentName: input.agentName,
|
|
44
|
+
record: {
|
|
45
|
+
...input.record,
|
|
46
|
+
kind: "failed_attempt",
|
|
47
|
+
status: input.record.status || "observed",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function recordRejectedApproach(input) {
|
|
52
|
+
return appendCognitiveArtifact({
|
|
53
|
+
workdir: input.workdir,
|
|
54
|
+
agentName: input.agentName,
|
|
55
|
+
record: {
|
|
56
|
+
...input.record,
|
|
57
|
+
kind: "rejected_approach",
|
|
58
|
+
status: input.record.status || "rejected",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function readCognitiveArtifacts(input) {
|
|
63
|
+
try {
|
|
64
|
+
const content = await readFile(cognitiveArtifactPath(input.workdir, input.agentName, input.kind), "utf-8");
|
|
65
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim());
|
|
66
|
+
const selected = typeof input.limit === "number" && input.limit > 0
|
|
67
|
+
? lines.slice(-input.limit)
|
|
68
|
+
: lines;
|
|
69
|
+
return selected.flatMap((line) => {
|
|
70
|
+
try {
|
|
71
|
+
return [JSON.parse(line)];
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function normalizeMemoryWeights(weights) {
|
|
83
|
+
return {
|
|
84
|
+
importance: clampScore(weights.importance),
|
|
85
|
+
confidence: clampScore(weights.confidence),
|
|
86
|
+
validation: clampScore(weights.validation),
|
|
87
|
+
userPriority: clampScore(weights.userPriority),
|
|
88
|
+
...(weights.relevance === undefined ? {} : { relevance: clampScore(weights.relevance) }),
|
|
89
|
+
...(weights.freshness === undefined ? {} : { freshness: clampScore(weights.freshness) }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function normalizeTags(tags) {
|
|
93
|
+
if (!Array.isArray(tags))
|
|
94
|
+
return [];
|
|
95
|
+
return [...new Set(tags.map((tag) => String(tag).trim()).filter(Boolean))];
|
|
96
|
+
}
|
|
97
|
+
function clampScore(value) {
|
|
98
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
99
|
+
return 0;
|
|
100
|
+
return Math.max(0, Math.min(1, value));
|
|
101
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { redactSecrets } from "./redaction.js";
|
|
5
|
+
const EVENT_FILE_BY_STREAM = {
|
|
6
|
+
main_chat: "main_chat.jsonl",
|
|
7
|
+
peripheral: "peripheral.jsonl",
|
|
8
|
+
cm: "cm.jsonl",
|
|
9
|
+
compute: "compute.jsonl",
|
|
10
|
+
task: "task.jsonl",
|
|
11
|
+
};
|
|
12
|
+
export function createCognitiveEventId(prefix = "evt") {
|
|
13
|
+
return `${prefix}_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
14
|
+
}
|
|
15
|
+
export function cognitiveEventsDir(workdir, agentName) {
|
|
16
|
+
return join(workdir, ".akemon", "agents", agentName, "events");
|
|
17
|
+
}
|
|
18
|
+
export function cognitiveEventLogPath(workdir, agentName, stream) {
|
|
19
|
+
return join(cognitiveEventsDir(workdir, agentName), EVENT_FILE_BY_STREAM[stream]);
|
|
20
|
+
}
|
|
21
|
+
export async function appendCognitiveEvent(input) {
|
|
22
|
+
const event = {
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
id: input.event.id || createCognitiveEventId(),
|
|
25
|
+
createdAt: input.event.createdAt || new Date().toISOString(),
|
|
26
|
+
...input.event,
|
|
27
|
+
};
|
|
28
|
+
const dir = cognitiveEventsDir(input.workdir, input.agentName);
|
|
29
|
+
await mkdir(dir, { recursive: true });
|
|
30
|
+
const path = cognitiveEventLogPath(input.workdir, input.agentName, event.stream);
|
|
31
|
+
await appendFile(path, `${JSON.stringify(redactSecrets(event))}\n`, "utf-8");
|
|
32
|
+
return event;
|
|
33
|
+
}
|
|
34
|
+
export async function readCognitiveEvents(input) {
|
|
35
|
+
try {
|
|
36
|
+
const path = cognitiveEventLogPath(input.workdir, input.agentName, input.stream);
|
|
37
|
+
const content = await readFile(path, "utf-8");
|
|
38
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim());
|
|
39
|
+
const selected = typeof input.limit === "number" && input.limit > 0
|
|
40
|
+
? lines.slice(-input.limit)
|
|
41
|
+
: lines;
|
|
42
|
+
return selected.map((line) => JSON.parse(line));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
4
|
import { randomBytes, randomUUID } from "crypto";
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { akemonHome } from "./akemon-home.js";
|
|
6
|
+
function configPath() {
|
|
7
|
+
return join(akemonHome(), "config.json");
|
|
8
|
+
}
|
|
8
9
|
export function generateKey(prefix = "ak") {
|
|
9
10
|
return prefix + "_" + randomBytes(24).toString("base64url");
|
|
10
11
|
}
|
|
11
12
|
export async function loadConfig() {
|
|
12
|
-
|
|
13
|
+
const path = configPath();
|
|
14
|
+
if (!existsSync(path))
|
|
13
15
|
return {};
|
|
14
|
-
const raw = await readFile(
|
|
16
|
+
const raw = await readFile(path, "utf-8");
|
|
15
17
|
return JSON.parse(raw);
|
|
16
18
|
}
|
|
17
19
|
export async function saveConfig(config) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const dir = akemonHome();
|
|
21
|
+
if (!existsSync(dir)) {
|
|
22
|
+
await mkdir(dir, { recursive: true });
|
|
20
23
|
}
|
|
21
|
-
await writeFile(
|
|
24
|
+
await writeFile(configPath(), JSON.stringify(config, null, 2));
|
|
25
|
+
}
|
|
26
|
+
export async function getLocalManagerName() {
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
return typeof config.local_manager === "string" && config.local_manager
|
|
29
|
+
? config.local_manager
|
|
30
|
+
: null;
|
|
31
|
+
}
|
|
32
|
+
export async function setLocalManagerName(name) {
|
|
33
|
+
const config = await loadConfig();
|
|
34
|
+
const cleaned = name.trim();
|
|
35
|
+
if (!cleaned)
|
|
36
|
+
throw new Error("Local manager name must not be empty");
|
|
37
|
+
config.local_manager = cleaned;
|
|
38
|
+
await saveConfig(config);
|
|
39
|
+
return cleaned;
|
|
22
40
|
}
|
|
23
41
|
// Legacy: single key for direct mode
|
|
24
42
|
export async function getOrCreateKey(explicitKey) {
|
|
@@ -35,6 +53,22 @@ export async function getOrCreateKey(explicitKey) {
|
|
|
35
53
|
await saveConfig(config);
|
|
36
54
|
return key;
|
|
37
55
|
}
|
|
56
|
+
export async function getOrCreateLocalOwnerSecret() {
|
|
57
|
+
const config = await loadConfig();
|
|
58
|
+
if (typeof config.local_owner_key === "string" && config.local_owner_key) {
|
|
59
|
+
return config.local_owner_key;
|
|
60
|
+
}
|
|
61
|
+
// Keep existing local installations usable: older local owner endpoints used
|
|
62
|
+
// secret_key even when relay was disabled.
|
|
63
|
+
if (typeof config.secret_key === "string" && config.secret_key) {
|
|
64
|
+
config.local_owner_key = config.secret_key;
|
|
65
|
+
await saveConfig(config);
|
|
66
|
+
return config.local_owner_key;
|
|
67
|
+
}
|
|
68
|
+
config.local_owner_key = generateKey("ak_local");
|
|
69
|
+
await saveConfig(config);
|
|
70
|
+
return config.local_owner_key;
|
|
71
|
+
}
|
|
38
72
|
export async function getOrCreateRelayCredentials() {
|
|
39
73
|
const config = await loadConfig();
|
|
40
74
|
let changed = false;
|
|
@@ -43,7 +77,9 @@ export async function getOrCreateRelayCredentials() {
|
|
|
43
77
|
changed = true;
|
|
44
78
|
}
|
|
45
79
|
if (!config.secret_key) {
|
|
46
|
-
config.secret_key =
|
|
80
|
+
config.secret_key = typeof config.local_owner_key === "string" && config.local_owner_key
|
|
81
|
+
? config.local_owner_key
|
|
82
|
+
: generateKey("ak_secret");
|
|
47
83
|
changed = true;
|
|
48
84
|
}
|
|
49
85
|
if (!config.access_key) {
|
package/dist/context.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { readFile, writeFile, mkdir, appendFile, readdir, stat, unlink } from "fs/promises";
|
|
16
16
|
import { join } from "path";
|
|
17
17
|
import { localNow } from "./self.js";
|
|
18
|
+
import { createConversationMessage, } from "./akemon-message.js";
|
|
18
19
|
function conversationsDir(workdir, agentName) {
|
|
19
20
|
return join(workdir, ".akemon", "agents", agentName, "conversations");
|
|
20
21
|
}
|
|
@@ -30,7 +31,7 @@ export function resolveConvId(publisherId, sessionId) {
|
|
|
30
31
|
return "ses_anonymous";
|
|
31
32
|
}
|
|
32
33
|
/** Parse a conversation markdown file into structured data. */
|
|
33
|
-
function parseConversation(content) {
|
|
34
|
+
function parseConversation(content, agentName = "unknown", convId = "unknown") {
|
|
34
35
|
let summary = "";
|
|
35
36
|
const rounds = [];
|
|
36
37
|
const summaryMatch = content.match(/## Summary\n([\s\S]*?)(?=\n## Recent|$)/);
|
|
@@ -44,16 +45,28 @@ function parseConversation(content) {
|
|
|
44
45
|
// Format: [ts] [kind] Role: text OR (legacy) [ts] Role: text
|
|
45
46
|
const m = line.match(/^\[(.+?)\] (?:\[(order)\] )?(User|Agent): (.*)$/);
|
|
46
47
|
if (m) {
|
|
48
|
+
const kind = m[2] ?? "chat";
|
|
49
|
+
const role = m[3];
|
|
50
|
+
const text = m[4];
|
|
47
51
|
rounds.push({
|
|
48
52
|
ts: m[1],
|
|
49
|
-
kind
|
|
50
|
-
role:
|
|
51
|
-
content:
|
|
53
|
+
kind,
|
|
54
|
+
role: role.toLowerCase(),
|
|
55
|
+
content: text,
|
|
56
|
+
message: createConversationMessage({
|
|
57
|
+
agentName,
|
|
58
|
+
conversationId: convId,
|
|
59
|
+
role,
|
|
60
|
+
text,
|
|
61
|
+
kind,
|
|
62
|
+
createdAt: m[1],
|
|
63
|
+
}),
|
|
52
64
|
});
|
|
53
65
|
}
|
|
54
66
|
else if (rounds.length > 0 && line !== "") {
|
|
55
67
|
// Continuation line — append to previous round
|
|
56
68
|
rounds[rounds.length - 1].content += "\n" + line;
|
|
69
|
+
rounds[rounds.length - 1].message.payload.text = rounds[rounds.length - 1].content;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
59
72
|
}
|
|
@@ -63,7 +76,7 @@ function parseConversation(content) {
|
|
|
63
76
|
export async function loadConversation(workdir, agentName, convId) {
|
|
64
77
|
try {
|
|
65
78
|
const content = await readFile(conversationPath(workdir, agentName, convId), "utf-8");
|
|
66
|
-
return parseConversation(content);
|
|
79
|
+
return parseConversation(content, agentName, convId);
|
|
67
80
|
}
|
|
68
81
|
catch {
|
|
69
82
|
return { summary: "", rounds: [] };
|
|
@@ -88,6 +101,14 @@ export async function appendMessage(workdir, agentName, convId, role, message, k
|
|
|
88
101
|
const kindTag = kind === "order" ? "[order] " : "";
|
|
89
102
|
content = content.trimEnd() + "\n" + `[${ts}] ${kindTag}${role}: ${message}` + "\n";
|
|
90
103
|
await writeFile(p, content);
|
|
104
|
+
return createConversationMessage({
|
|
105
|
+
agentName,
|
|
106
|
+
conversationId: convId,
|
|
107
|
+
role,
|
|
108
|
+
text: message,
|
|
109
|
+
kind,
|
|
110
|
+
createdAt: ts,
|
|
111
|
+
});
|
|
91
112
|
}
|
|
92
113
|
/** Append a user+agent round to a conversation file. Creates file if needed. */
|
|
93
114
|
export async function appendRound(workdir, agentName, convId, userMsg, agentMsg) {
|
|
@@ -144,7 +165,7 @@ export async function listConversations(workdir, agentName) {
|
|
|
144
165
|
try {
|
|
145
166
|
const st = await stat(join(dir, f));
|
|
146
167
|
const content = await readFile(join(dir, f), "utf-8");
|
|
147
|
-
const conv = parseConversation(content);
|
|
168
|
+
const conv = parseConversation(content, agentName, id);
|
|
148
169
|
results.push({
|
|
149
170
|
id,
|
|
150
171
|
lastActive: st.mtime.toISOString(),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|