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.
Files changed (51) hide show
  1. package/DATA_POLICY.md +11 -3
  2. package/README.md +133 -21
  3. package/dist/akemon-home.js +56 -0
  4. package/dist/akemon-message.js +107 -0
  5. package/dist/best-effort.js +8 -0
  6. package/dist/cli.js +1188 -100
  7. package/dist/cognitive-artifact-store.js +101 -0
  8. package/dist/cognitive-event-log.js +47 -0
  9. package/dist/config.js +45 -9
  10. package/dist/context.js +27 -6
  11. package/dist/core/contracts/layers.js +1 -0
  12. package/dist/core/contracts/permission.js +1 -0
  13. package/dist/core/contracts/workspace.js +1 -0
  14. package/dist/core-cognitive-module.js +768 -0
  15. package/dist/engine-peripheral.js +127 -26
  16. package/dist/engine-routing.js +58 -17
  17. package/dist/interactive-session.js +361 -0
  18. package/dist/local-interconnect.js +156 -0
  19. package/dist/local-registry.js +178 -0
  20. package/dist/mcp-server.js +4 -1
  21. package/dist/memory-proposal.js +379 -0
  22. package/dist/memory-recorder.js +368 -0
  23. package/dist/orphan-scan.js +36 -24
  24. package/dist/passive-reflection-cognitive-module.js +172 -0
  25. package/dist/peripheral-registry.js +235 -0
  26. package/dist/permission-audit.js +132 -0
  27. package/dist/relay-client.js +68 -9
  28. package/dist/relay-mode.js +34 -0
  29. package/dist/relay-peripheral.js +139 -49
  30. package/dist/runtime-platform.js +122 -0
  31. package/dist/secretariat/client.js +87 -0
  32. package/dist/self.js +15 -6
  33. package/dist/server.js +3675 -512
  34. package/dist/social-discovery.js +231 -0
  35. package/dist/software-agent-peripheral.js +185 -244
  36. package/dist/software-agent-transport.js +177 -0
  37. package/dist/task-module.js +243 -0
  38. package/dist/task-registry.js +756 -0
  39. package/dist/vendor/xterm/addon-fit.js +2 -0
  40. package/dist/vendor/xterm/addon-search.js +2 -0
  41. package/dist/vendor/xterm/addon-web-links.js +2 -0
  42. package/dist/vendor/xterm/xterm.css +285 -0
  43. package/dist/vendor/xterm/xterm.js +2 -0
  44. package/dist/work-memory.js +59 -15
  45. package/dist/workbench-peripheral-guide.js +79 -0
  46. package/dist/workbench-session.js +1074 -0
  47. package/dist/workbench.html +4011 -0
  48. package/package.json +8 -3
  49. package/scripts/build.cjs +24 -0
  50. package/scripts/check-architecture-baseline.cjs +68 -0
  51. package/scripts/test.cjs +38 -0
@@ -8,9 +8,53 @@
8
8
  * Migration strategy: server.ts can import and use this directly — no need for
9
9
  * the full Core/EventBus wiring yet. That comes later.
10
10
  */
11
+ import { createHash } from "crypto";
11
12
  import { readFile, writeFile, mkdir } from "fs/promises";
12
13
  import { join } from "path";
13
- import { gamesDir, notesDir, pagesDir, selfDir, loadLatestIdentity, loadRecentCanvasEntries, loadGameList, loadGame, loadNotesList, loadNote, loadPageList, loadPage, loadDirectives, directivesSummary, loadBioState, } from "./self.js";
14
+ import { agentRelayImportsDir } from "./akemon-home.js";
15
+ import { actorRef } from "./akemon-message.js";
16
+ import { appendPermissionAuditRecord } from "./permission-audit.js";
17
+ import { logBestEffortError } from "./best-effort.js";
18
+ import { selfDir, loadLatestIdentity, loadRecentCanvasEntries, loadGameList, loadGame, loadNotesList, loadNote, loadPageList, loadPage, loadDirectives, directivesSummary, loadBioState, } from "./self.js";
19
+ function shortHash(value) {
20
+ return createHash("sha256").update(value).digest("hex").slice(0, 8);
21
+ }
22
+ function safeRelayArtifactSlug(slug) {
23
+ const raw = String(slug || "untitled").trim() || "untitled";
24
+ const base = raw
25
+ .replace(/[^A-Za-z0-9._-]+/g, "_")
26
+ .replace(/^\.+/, "")
27
+ .replace(/[. ]+$/g, "")
28
+ .slice(0, 100) || "untitled";
29
+ const portableBase = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(base) ? `_${base}` : base;
30
+ return portableBase === raw && raw.length <= 100 ? portableBase : `${portableBase}-${shortHash(raw)}`;
31
+ }
32
+ async function stageRelayArtifactImport(input) {
33
+ const dir = join(agentRelayImportsDir(input.agentName), input.kind);
34
+ await mkdir(dir, { recursive: true });
35
+ const filename = `${safeRelayArtifactSlug(input.slug)}.${input.extension}`;
36
+ const artifactPath = join(dir, filename);
37
+ try {
38
+ await readFile(artifactPath, "utf-8");
39
+ return false;
40
+ }
41
+ catch { }
42
+ await writeFile(artifactPath, input.content, "utf-8");
43
+ await writeFile(`${artifactPath}.relay-import.json`, JSON.stringify({
44
+ schemaVersion: 1,
45
+ source: "relay",
46
+ authority: "remote-projection",
47
+ canonicalSelfMemory: false,
48
+ importedInto: "inbox",
49
+ relayAgent: input.agentName,
50
+ kind: input.kind,
51
+ originalSlug: input.slug,
52
+ localFile: filename,
53
+ sourceUrl: input.sourceUrl,
54
+ importedAt: new Date().toISOString(),
55
+ }, null, 2) + "\n", "utf-8");
56
+ return true;
57
+ }
14
58
  export class RelayPeripheral {
15
59
  id = "relay";
16
60
  name = "Akemon Relay";
@@ -266,7 +310,9 @@ export class RelayPeripheral {
266
310
  signal: AbortSignal.timeout(10_000),
267
311
  });
268
312
  }
269
- catch { }
313
+ catch (error) {
314
+ logBestEffortError("relay session context set", error);
315
+ }
270
316
  }
271
317
  // ---------------------------------------------------------------------------
272
318
  // 10. Explore — plain-text environment briefing for Modules
@@ -406,6 +452,9 @@ export class RelayPeripheral {
406
452
  personality: bio.personality,
407
453
  },
408
454
  });
455
+ let syncedGames = 0;
456
+ let syncedNotes = 0;
457
+ let syncedPages = 0;
409
458
  // Sync games
410
459
  try {
411
460
  const localGames = await loadGameList(workdir, agentName);
@@ -413,10 +462,13 @@ export class RelayPeripheral {
413
462
  const html = await loadGame(workdir, agentName, g.slug);
414
463
  if (html && html.includes("<!DOCTYPE html>")) {
415
464
  this.syncGame(g.slug, g.title, g.description, html);
465
+ syncedGames++;
416
466
  }
417
467
  }
418
468
  }
419
- catch { }
469
+ catch (error) {
470
+ logBestEffortError("relay sync games", error);
471
+ }
420
472
  // Sync notes
421
473
  try {
422
474
  const localNotes = await loadNotesList(workdir, agentName);
@@ -424,10 +476,13 @@ export class RelayPeripheral {
424
476
  const content = await loadNote(workdir, agentName, n.slug);
425
477
  if (content) {
426
478
  this.syncNote(n.slug, n.title, content);
479
+ syncedNotes++;
427
480
  }
428
481
  }
429
482
  }
430
- catch { }
483
+ catch (error) {
484
+ logBestEffortError("relay sync notes", error);
485
+ }
431
486
  // Sync pages
432
487
  try {
433
488
  const localPages = await loadPageList(workdir, agentName);
@@ -435,82 +490,117 @@ export class RelayPeripheral {
435
490
  const html = await loadPage(workdir, agentName, p.slug);
436
491
  if (html && html.includes("<!DOCTYPE html>")) {
437
492
  this.syncPage(p.slug, p.title, p.description, html);
493
+ syncedPages++;
438
494
  }
439
495
  }
440
496
  }
441
- catch { }
497
+ catch (error) {
498
+ logBestEffortError("relay sync pages", error);
499
+ }
500
+ try {
501
+ await appendPermissionAuditRecord(agentName, {
502
+ actionKind: "relay-publication",
503
+ action: "relay.sync-to-relay",
504
+ requestedBy: actorRef("system", "relay-sync", "local"),
505
+ performedBy: actorRef("agent", agentName, "relay"),
506
+ target: actorRef("relay", this.baseUrl, "relay"),
507
+ riskLevel: "low",
508
+ memoryScope: "public",
509
+ workdir,
510
+ projectScope: "global",
511
+ transport: "relay",
512
+ decision: {
513
+ result: "allowed",
514
+ mode: "automatic",
515
+ reason: "Relay peripheral published local public projections to relay.",
516
+ },
517
+ metadata: {
518
+ fields: ["self_intro", "canvas", "mood", "profile_html", "directives_summary", "bio_state"],
519
+ broadcast: !!broadcast,
520
+ syncedGames,
521
+ syncedNotes,
522
+ syncedPages,
523
+ },
524
+ });
525
+ }
526
+ catch (error) {
527
+ logBestEffortError("relay sync audit append", error);
528
+ }
442
529
  }
443
- /** Pull games/notes/pages from relay to local — restores data on restart */
444
- async pullFromRelay(workdir, agentName) {
530
+ /**
531
+ * Pull games/notes/pages from relay into the local inbox.
532
+ * Relay artifacts are external projections, not canonical self memory.
533
+ */
534
+ async pullFromRelay(_workdir, agentName) {
445
535
  const baseUrl = `${this.config.httpUrl}/v1/agent/${encodeURIComponent(agentName)}`;
446
- let pulled = 0;
447
- // Pull games
536
+ let staged = 0;
537
+ // Stage games
448
538
  try {
449
- const gDir = gamesDir(workdir, agentName);
450
- await mkdir(gDir, { recursive: true });
451
- const res = await fetch(`${baseUrl}/games`);
539
+ const listUrl = `${baseUrl}/games`;
540
+ const res = await fetch(listUrl);
452
541
  if (res.ok) {
453
542
  const games = await res.json();
454
543
  for (const g of games) {
455
- if (!g.html)
544
+ if (!g.slug || !g.html)
456
545
  continue;
457
- const path = join(gDir, `${g.slug}.html`);
458
- try {
459
- await readFile(path, "utf-8");
460
- }
461
- catch {
462
- await writeFile(path, g.html);
463
- pulled++;
464
- }
546
+ if (await stageRelayArtifactImport({
547
+ agentName,
548
+ kind: "games",
549
+ slug: g.slug,
550
+ extension: "html",
551
+ content: g.html,
552
+ sourceUrl: `${baseUrl}/games/${encodeURIComponent(g.slug)}`,
553
+ }))
554
+ staged++;
465
555
  }
466
556
  }
467
557
  }
468
558
  catch { }
469
- // Pull notes
559
+ // Stage notes
470
560
  try {
471
- const nDir = notesDir(workdir, agentName);
472
- await mkdir(nDir, { recursive: true });
473
- const res = await fetch(`${baseUrl}/notes`);
561
+ const listUrl = `${baseUrl}/notes`;
562
+ const res = await fetch(listUrl);
474
563
  if (res.ok) {
475
564
  const notes = await res.json();
476
565
  for (const n of notes) {
477
- if (!n.content)
566
+ if (!n.slug || !n.content)
478
567
  continue;
479
- const path = join(nDir, `${n.slug}.md`);
480
- try {
481
- await readFile(path, "utf-8");
482
- }
483
- catch {
484
- await writeFile(path, n.content);
485
- pulled++;
486
- }
568
+ if (await stageRelayArtifactImport({
569
+ agentName,
570
+ kind: "notes",
571
+ slug: n.slug,
572
+ extension: "md",
573
+ content: n.content,
574
+ sourceUrl: `${baseUrl}/notes/${encodeURIComponent(n.slug)}`,
575
+ }))
576
+ staged++;
487
577
  }
488
578
  }
489
579
  }
490
580
  catch { }
491
- // Pull pages
581
+ // Stage pages
492
582
  try {
493
- const pDir = pagesDir(workdir, agentName);
494
- await mkdir(pDir, { recursive: true });
495
- const res = await fetch(`${baseUrl}/pages`);
583
+ const listUrl = `${baseUrl}/pages`;
584
+ const res = await fetch(listUrl);
496
585
  if (res.ok) {
497
586
  const pages = await res.json();
498
587
  for (const p of pages) {
499
- if (!p.html)
588
+ if (!p.slug || !p.html)
500
589
  continue;
501
- const path = join(pDir, `${p.slug}.html`);
502
- try {
503
- await readFile(path, "utf-8");
504
- }
505
- catch {
506
- await writeFile(path, p.html);
507
- pulled++;
508
- }
590
+ if (await stageRelayArtifactImport({
591
+ agentName,
592
+ kind: "pages",
593
+ slug: p.slug,
594
+ extension: "html",
595
+ content: p.html,
596
+ sourceUrl: `${baseUrl}/pages/${encodeURIComponent(p.slug)}`,
597
+ }))
598
+ staged++;
509
599
  }
510
600
  }
511
601
  }
512
602
  catch { }
513
- if (pulled > 0)
514
- console.log(`[sync] Pulled ${pulled} items from relay`);
603
+ if (staged > 0)
604
+ console.log(`[sync] Staged ${staged} relay items in inbox/relay-imports`);
515
605
  }
516
606
  }
@@ -0,0 +1,122 @@
1
+ import { spawn } from "child_process";
2
+ import { tmpdir } from "os";
3
+ export function getRuntimePlatform(platform = process.platform) {
4
+ return {
5
+ platform,
6
+ tempDir: tmpdir(),
7
+ defaultShell: defaultShellForPlatform(platform),
8
+ capabilities: runtimeCapabilities(platform),
9
+ };
10
+ }
11
+ export function runtimeCapabilities(platform = process.platform) {
12
+ const isPosixDesktop = platform === "darwin" || platform === "linux";
13
+ return {
14
+ canSpawnProcess: true,
15
+ canOpenBrowser: platform === "darwin" || platform === "linux" || platform === "win32",
16
+ canStartPty: isPosixDesktop,
17
+ canKillProcessTree: isPosixDesktop || platform === "win32",
18
+ canScanOrphanProcesses: isPosixDesktop,
19
+ supportsProcessGroups: isPosixDesktop,
20
+ };
21
+ }
22
+ export function defaultShellForPlatform(platform = process.platform) {
23
+ if (platform === "win32")
24
+ return process.env.ComSpec || process.env.COMSPEC || "cmd.exe";
25
+ return process.env.SHELL || "/bin/sh";
26
+ }
27
+ export async function openUrl(url, platform = process.platform) {
28
+ const command = openUrlCommand(url, platform);
29
+ if (!command) {
30
+ throw new Error(`Opening browser is not supported on platform: ${platform}`);
31
+ }
32
+ await new Promise((resolve, reject) => {
33
+ const child = spawn(command.command, command.args, {
34
+ stdio: "ignore",
35
+ detached: true,
36
+ windowsHide: true,
37
+ });
38
+ child.once("error", reject);
39
+ child.once("spawn", () => {
40
+ child.unref();
41
+ resolve();
42
+ });
43
+ });
44
+ }
45
+ export function openUrlCommand(url, platform = process.platform) {
46
+ if (platform === "darwin")
47
+ return { command: "open", args: [url] };
48
+ if (platform === "linux")
49
+ return { command: "xdg-open", args: [url] };
50
+ if (platform === "win32")
51
+ return { command: "cmd", args: ["/c", "start", "", url] };
52
+ return null;
53
+ }
54
+ export function shouldDetachChildProcess(platform = process.platform) {
55
+ return runtimeCapabilities(platform).supportsProcessGroups;
56
+ }
57
+ export function terminateProcessTree(pid, options = {}, platform = process.platform) {
58
+ if (!Number.isInteger(pid) || pid <= 0)
59
+ return;
60
+ const signal = options.signal || "SIGTERM";
61
+ if (platform === "win32") {
62
+ runTaskkill(pid, signal === "SIGKILL");
63
+ }
64
+ else if (runtimeCapabilities(platform).supportsProcessGroups) {
65
+ try {
66
+ process.kill(-pid, signal);
67
+ }
68
+ catch { }
69
+ }
70
+ else {
71
+ try {
72
+ process.kill(pid, signal);
73
+ }
74
+ catch { }
75
+ }
76
+ if (options.forceAfterMs !== undefined && signal !== "SIGKILL") {
77
+ const timer = setTimeout(() => {
78
+ terminateProcessTree(pid, { signal: "SIGKILL" }, platform);
79
+ }, options.forceAfterMs);
80
+ timer.unref?.();
81
+ }
82
+ }
83
+ export function isProcessAlive(pid) {
84
+ if (!Number.isInteger(pid) || pid <= 0)
85
+ return false;
86
+ try {
87
+ process.kill(pid, 0);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ return error.code === "EPERM";
92
+ }
93
+ }
94
+ export function assertPortablePathSegment(value, label = "path segment") {
95
+ const cleaned = value.trim();
96
+ if (!cleaned)
97
+ throw new Error(`Invalid ${label}: must not be empty`);
98
+ if (/[/\\]/.test(cleaned))
99
+ throw new Error(`Invalid ${label}: must not contain path separators`);
100
+ if (/[<>:"|?*\x00-\x1f\x7f]/.test(cleaned)) {
101
+ throw new Error(`Invalid ${label}: contains characters that are not portable across filesystems`);
102
+ }
103
+ if (/[. ]$/.test(cleaned)) {
104
+ throw new Error(`Invalid ${label}: must not end with a dot or space`);
105
+ }
106
+ const upper = cleaned.toUpperCase();
107
+ if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\..*)?$/.test(upper)) {
108
+ throw new Error(`Invalid ${label}: reserved Windows device name`);
109
+ }
110
+ return cleaned;
111
+ }
112
+ function runTaskkill(pid, force) {
113
+ const args = ["/PID", String(pid), "/T"];
114
+ if (force)
115
+ args.push("/F");
116
+ const child = spawn("taskkill", args, {
117
+ stdio: "ignore",
118
+ windowsHide: true,
119
+ });
120
+ child.on("error", () => { });
121
+ child.unref();
122
+ }
@@ -0,0 +1,87 @@
1
+ import { actorRef, createAkemonMessage } from "../akemon-message.js";
2
+ export class SecretariatClient {
3
+ requestJson;
4
+ constructor(transport) {
5
+ this.requestJson = transport.requestJson;
6
+ }
7
+ submitOwnerIntent(input) {
8
+ return this.requestJson("/self/message", {
9
+ method: "POST",
10
+ body: JSON.stringify({
11
+ message: input.message,
12
+ ...(input.wait === false ? { wait: false } : {}),
13
+ }),
14
+ });
15
+ }
16
+ queueOwnerFollowUp(input) {
17
+ const message = {
18
+ ...input.message,
19
+ metadata: {
20
+ ...(input.message.metadata || {}),
21
+ followUpForTaskId: input.taskId,
22
+ },
23
+ };
24
+ return this.requestJson("/self/message", {
25
+ method: "POST",
26
+ body: JSON.stringify({ message }),
27
+ });
28
+ }
29
+ async getTaskStatus(taskId) {
30
+ return this.requestJson(`/self/tasks/${encodeURIComponent(taskId)}`, { method: "GET" });
31
+ }
32
+ async getTaskReport(taskId) {
33
+ return this.getTaskStatus(taskId);
34
+ }
35
+ async listConversationTasks(input = {}) {
36
+ const params = new URLSearchParams();
37
+ params.set("limit", String(input.limit || 50));
38
+ if (input.status)
39
+ params.set("status", input.status);
40
+ if (input.route)
41
+ params.set("route", input.route);
42
+ if (input.visibility)
43
+ params.set("visibility", input.visibility);
44
+ const response = await this.requestJson(`/self/tasks?${params.toString()}`, { method: "GET" });
45
+ if (!input.conversationId)
46
+ return response;
47
+ const tasks = (response.tasks || []).filter((task) => task.conversationId === input.conversationId);
48
+ const taskIds = new Set(tasks.map((task) => task.taskId));
49
+ return {
50
+ ...response,
51
+ tasks,
52
+ taskViews: (response.taskViews || []).filter((view) => taskIds.has(view.taskId)),
53
+ };
54
+ }
55
+ }
56
+ export function createOwnerChatMessage(input) {
57
+ return createAkemonMessage({
58
+ type: "akemon.message",
59
+ id: input.id,
60
+ createdAt: input.createdAt,
61
+ source: actorRef("owner", "owner", "local"),
62
+ target: actorRef("agent", input.targetAgent, "local"),
63
+ conversationId: input.conversationId,
64
+ memoryScope: input.memoryScope || "owner",
65
+ permissions: {},
66
+ transport: "local",
67
+ metadata: input.metadata,
68
+ payload: {
69
+ text: input.text,
70
+ format: "text",
71
+ kind: "chat",
72
+ },
73
+ });
74
+ }
75
+ export function taskIdFromOwnerChatMessage(message) {
76
+ const suffix = message.id || `${message.conversationId || "local"}_${Date.now()}`;
77
+ return `task_${suffix.replace(/[^a-zA-Z0-9_.:-]/g, "_")}`;
78
+ }
79
+ export function taskIsTerminal(task) {
80
+ if (!task)
81
+ return false;
82
+ return task.stage === "done"
83
+ || task.stage === "blocked"
84
+ || task.status === "succeeded"
85
+ || task.status === "failed"
86
+ || task.status === "info";
87
+ }
package/dist/self.js CHANGED
@@ -4,10 +4,12 @@
4
4
  * Manages the agent's self-awareness: world knowledge, first-person memory,
5
5
  * identity (five questions), bio-simulation, and inner canvas.
6
6
  *
7
- * All data lives in .akemon/agents/{name}/self/ — separate from work context.
7
+ * All self data lives in Akemon home under agents/{name}/self/ — separate from
8
+ * project work context.
8
9
  */
9
10
  import { readFile, writeFile, appendFile, mkdir, readdir } from "fs/promises";
10
11
  import { join } from "path";
12
+ import { agentConfigPath as homeAgentConfigPath, agentAuditDir, agentContactsDir, agentHomeDir, agentInboxDir, agentSelfDir, globalWorkMemoryDir, } from "./akemon-home.js";
11
13
  /** Local timestamp string like "2026-03-26T19:13:26" (server timezone, no Z suffix) */
12
14
  export function localNow() {
13
15
  const d = new Date();
@@ -21,8 +23,8 @@ export function localNowFilename() {
21
23
  // ---------------------------------------------------------------------------
22
24
  // Paths
23
25
  // ---------------------------------------------------------------------------
24
- export function selfDir(workdir, agentName) {
25
- return join(workdir, ".akemon", "agents", agentName, "self");
26
+ export function selfDir(_workdir, agentName) {
27
+ return agentSelfDir(agentName);
26
28
  }
27
29
  function worldPath(workdir, agentName) {
28
30
  return join(selfDir(workdir, agentName), "world.md");
@@ -60,8 +62,8 @@ export function guidePath(workdir, agentName) {
60
62
  export function biosPath(workdir, agentName) {
61
63
  return join(selfDir(workdir, agentName), "bios.md");
62
64
  }
63
- function agentConfigPath(workdir, agentName) {
64
- return join(workdir, ".akemon", "agents", agentName, "config.json");
65
+ function agentConfigPath(_workdir, agentName) {
66
+ return homeAgentConfigPath(agentName);
65
67
  }
66
68
  export function directivesPath(workdir, agentName) {
67
69
  return join(selfDir(workdir, agentName), "tasks.md");
@@ -139,6 +141,7 @@ const DEFAULT_CONFIG = {
139
141
  auto_offline_enabled: true,
140
142
  hunger_decay_interval: 300_000, // 5 minutes per hunger point (was 30s — way too fast)
141
143
  context_budget: 4096,
144
+ owner_language: "auto",
142
145
  };
143
146
  export async function initAgentConfig(workdir, agentName) {
144
147
  const p = agentConfigPath(workdir, agentName);
@@ -146,8 +149,14 @@ export async function initAgentConfig(workdir, agentName) {
146
149
  await readFile(p, "utf-8");
147
150
  }
148
151
  catch {
149
- await mkdir(join(workdir, ".akemon", "agents", agentName), { recursive: true });
152
+ const home = agentHomeDir(agentName);
153
+ await mkdir(selfDir(workdir, agentName), { recursive: true });
154
+ await mkdir(globalWorkMemoryDir(agentName), { recursive: true });
155
+ await mkdir(agentAuditDir(agentName), { recursive: true });
156
+ await mkdir(agentContactsDir(agentName), { recursive: true });
157
+ await mkdir(agentInboxDir(agentName), { recursive: true });
150
158
  await writeFile(p, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
159
+ console.log(`[self] First use for agent "${agentName}"; created identity home at ${home}`);
151
160
  console.log(`[self] Created config.json with defaults`);
152
161
  }
153
162
  }