@rubytech/taskmaster 1.0.88 → 1.0.90
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/build-info.json
CHANGED
|
@@ -10,6 +10,7 @@ import os from "node:os";
|
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { loadConfig, writeConfigFile } from "../config/io.js";
|
|
12
12
|
import { resolveConfigPath, DEFAULT_GATEWAY_PORT } from "../config/paths.js";
|
|
13
|
+
import { ensureMemoryLayout } from "../memory/layout.js";
|
|
13
14
|
import { runCommandWithTimeout } from "../process/exec.js";
|
|
14
15
|
import { resolveTaskmasterPackageRoot } from "../infra/taskmaster-root.js";
|
|
15
16
|
import { buildSeedConfig, buildDefaultAgentList } from "./provision-seed.js";
|
|
@@ -47,6 +48,8 @@ async function runProvision(opts) {
|
|
|
47
48
|
await copyTemplateWorkspace(workspace, force);
|
|
48
49
|
// Step 3: Write agent list to config
|
|
49
50
|
await writeAgentList(workspace);
|
|
51
|
+
// Step 3b: Set up shared memory layout with per-subfolder symlinks
|
|
52
|
+
await setupMemoryLayout(workspace);
|
|
50
53
|
// Step 4-6: Linux-only mDNS setup
|
|
51
54
|
if (skipPlatform) {
|
|
52
55
|
console.log("[4/7] Avahi: handled by install script");
|
|
@@ -154,6 +157,23 @@ async function writeAgentList(workspace) {
|
|
|
154
157
|
console.log("[3/7] Agent list: written (public + admin)");
|
|
155
158
|
}
|
|
156
159
|
// ---------------------------------------------------------------------------
|
|
160
|
+
// Step 3b: Memory layout
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
async function setupMemoryLayout(workspace) {
|
|
163
|
+
const agents = buildDefaultAgentList(workspace);
|
|
164
|
+
const agentDirs = agents.map((a) => ({
|
|
165
|
+
dir: a.workspace ?? path.join(workspace, "agents", a.id),
|
|
166
|
+
id: a.id,
|
|
167
|
+
}));
|
|
168
|
+
try {
|
|
169
|
+
await ensureMemoryLayout(workspace, agentDirs);
|
|
170
|
+
console.log("[3b/7] Memory layout: shared public/shared with per-agent symlinks");
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.error(`[3b/7] Memory layout failed: ${String(err)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
157
177
|
// Step 4: Install avahi/mDNS (Linux only)
|
|
158
178
|
// ---------------------------------------------------------------------------
|
|
159
179
|
async function installAvahi() {
|
|
@@ -168,9 +168,10 @@ function resolveTemplateDir() {
|
|
|
168
168
|
/** Copy directory tree recursively, skipping symlink targets for memory (recreates symlinks). */
|
|
169
169
|
async function cloneWorkspaceStructure(sourceDir, destDir) {
|
|
170
170
|
await fs.mkdir(destDir, { recursive: true });
|
|
171
|
-
// Copy agents/ (
|
|
171
|
+
// Copy agents/ (skip memory — handled by ensureMemoryLayout)
|
|
172
172
|
const agentsSrc = path.join(sourceDir, "agents");
|
|
173
173
|
const agentsDest = path.join(destDir, "agents");
|
|
174
|
+
const clonedAgents = [];
|
|
174
175
|
try {
|
|
175
176
|
const agentEntries = await fs.readdir(agentsSrc, { withFileTypes: true });
|
|
176
177
|
for (const entry of agentEntries) {
|
|
@@ -179,32 +180,20 @@ async function cloneWorkspaceStructure(sourceDir, destDir) {
|
|
|
179
180
|
const srcAgentDir = path.join(agentsSrc, entry.name);
|
|
180
181
|
const destAgentDir = path.join(agentsDest, entry.name);
|
|
181
182
|
await fs.mkdir(destAgentDir, { recursive: true });
|
|
182
|
-
|
|
183
|
+
clonedAgents.push({ dir: destAgentDir, id: entry.name });
|
|
184
|
+
// Copy non-symlink, non-memory files from agent dir
|
|
183
185
|
const agentFiles = await fs.readdir(srcAgentDir, { withFileTypes: true });
|
|
184
|
-
let hasMemoryLink = false;
|
|
185
186
|
for (const file of agentFiles) {
|
|
187
|
+
if (file.name === "memory")
|
|
188
|
+
continue;
|
|
189
|
+
if (file.isSymbolicLink())
|
|
190
|
+
continue;
|
|
186
191
|
const srcPath = path.join(srcAgentDir, file.name);
|
|
187
192
|
const destPath = path.join(destAgentDir, file.name);
|
|
188
|
-
if (file.
|
|
189
|
-
// Memory symlink — recreate pointing to same relative target
|
|
190
|
-
const target = await fs.readlink(srcPath);
|
|
191
|
-
await fs.symlink(target, destPath).catch(() => {
|
|
192
|
-
/* ignore if exists */
|
|
193
|
-
});
|
|
194
|
-
if (file.name === "memory")
|
|
195
|
-
hasMemoryLink = true;
|
|
196
|
-
}
|
|
197
|
-
else if (file.isFile()) {
|
|
193
|
+
if (file.isFile()) {
|
|
198
194
|
await fs.copyFile(srcPath, destPath);
|
|
199
195
|
}
|
|
200
196
|
}
|
|
201
|
-
// Always ensure memory symlink exists (templates may not include them)
|
|
202
|
-
if (!hasMemoryLink) {
|
|
203
|
-
const memoryLink = path.join(destAgentDir, "memory");
|
|
204
|
-
await fs.symlink("../../memory", memoryLink).catch(() => {
|
|
205
|
-
/* ignore if exists */
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
197
|
}
|
|
209
198
|
}
|
|
210
199
|
catch {
|
|
@@ -224,12 +213,11 @@ async function cloneWorkspaceStructure(sourceDir, destDir) {
|
|
|
224
213
|
catch {
|
|
225
214
|
/* source has no skills/ — skip */
|
|
226
215
|
}
|
|
227
|
-
// Create
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
await fs.mkdir(path.join(memoryDir, "users"), { recursive: true });
|
|
216
|
+
// Create shared memory structure with per-subfolder symlinks
|
|
217
|
+
if (clonedAgents.length > 0) {
|
|
218
|
+
const { ensureMemoryLayout } = await import("../../memory/layout.js");
|
|
219
|
+
await ensureMemoryLayout(destDir, clonedAgents);
|
|
220
|
+
}
|
|
233
221
|
}
|
|
234
222
|
async function copyDirRecursive(src, dest) {
|
|
235
223
|
await fs.mkdir(dest, { recursive: true });
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
|
2
|
+
import { listAgentIds, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
|
2
3
|
import { loadModelCatalog } from "../agents/model-catalog.js";
|
|
3
4
|
import { getModelRefStatus, resolveConfiguredModelRef, resolveHooksGmailModel, } from "../agents/model-selection.js";
|
|
4
5
|
import { isTruthyEnvValue } from "../infra/env.js";
|
|
6
|
+
import { ensureMemoryLayout } from "../memory/layout.js";
|
|
5
7
|
import { startGmailWatcher } from "../hooks/gmail-watcher.js";
|
|
6
8
|
import { clearInternalHooks, createInternalHookEvent, triggerInternalHook, } from "../hooks/internal-hooks.js";
|
|
7
9
|
import { loadInternalHooks } from "../hooks/loader.js";
|
|
@@ -25,6 +27,9 @@ export async function startGatewaySidecars(params) {
|
|
|
25
27
|
void recoverOrphanedSessions()
|
|
26
28
|
.then(() => stripBase64FromTranscripts())
|
|
27
29
|
.catch(() => { });
|
|
30
|
+
// Ensure memory directory layout: shared public/shared with per-agent symlinks.
|
|
31
|
+
// Self-healing — fixes broken/missing symlinks from prior versions.
|
|
32
|
+
void ensureMemoryLayoutFromConfig(params.cfg).catch(() => { });
|
|
28
33
|
// Start Gmail watcher if configured (hooks.gmail.account).
|
|
29
34
|
if (!isTruthyEnvValue(process.env.TASKMASTER_SKIP_GMAIL_WATCHER)) {
|
|
30
35
|
try {
|
|
@@ -129,3 +134,18 @@ export async function startGatewaySidecars(params) {
|
|
|
129
134
|
}
|
|
130
135
|
return { browserControl, pluginServices };
|
|
131
136
|
}
|
|
137
|
+
async function ensureMemoryLayoutFromConfig(cfg) {
|
|
138
|
+
const agentIds = listAgentIds(cfg);
|
|
139
|
+
if (agentIds.length < 2)
|
|
140
|
+
return; // Single-agent — no shared layout needed
|
|
141
|
+
const agentDirs = agentIds.map((id) => ({
|
|
142
|
+
dir: resolveAgentWorkspaceDir(cfg, id),
|
|
143
|
+
id,
|
|
144
|
+
}));
|
|
145
|
+
// Derive workspace root from agent dirs (strip /agents/{id} suffix)
|
|
146
|
+
const first = agentDirs[0].dir.replace(/\/+$/, "");
|
|
147
|
+
const match = first.match(/^(.+)\/agents\/[^/]+$/);
|
|
148
|
+
if (!match)
|
|
149
|
+
return; // Not a multi-agent workspace layout
|
|
150
|
+
await ensureMemoryLayout(match[1], agentDirs);
|
|
151
|
+
}
|
package/dist/memory/internal.js
CHANGED
|
@@ -34,6 +34,22 @@ async function walkDir(dir, files) {
|
|
|
34
34
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
35
35
|
for (const entry of entries) {
|
|
36
36
|
const full = path.join(dir, entry.name);
|
|
37
|
+
// Symlinks need fs.stat to determine the target type
|
|
38
|
+
if (entry.isSymbolicLink()) {
|
|
39
|
+
try {
|
|
40
|
+
const stat = await fs.stat(full);
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
await walkDir(full, files);
|
|
43
|
+
}
|
|
44
|
+
else if (stat.isFile() && entry.name.endsWith(".md")) {
|
|
45
|
+
files.push(full);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Broken symlink — skip
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
37
53
|
if (entry.isDirectory()) {
|
|
38
54
|
await walkDir(full, files);
|
|
39
55
|
continue;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory directory layout management.
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* ~/taskmaster/memory/ ← shared canonical location
|
|
6
|
+
* public/ ← shared: both agents read/write
|
|
7
|
+
* shared/ ← shared: both agents read/write
|
|
8
|
+
* ~/taskmaster/agents/admin/memory/ ← physical directory
|
|
9
|
+
* admin/ ← physical: admin-only data
|
|
10
|
+
* public → ../../../memory/public (symlink to shared)
|
|
11
|
+
* shared → ../../../memory/shared (symlink to shared)
|
|
12
|
+
* users/ ← physical: admin's per-user data
|
|
13
|
+
* ~/taskmaster/agents/public/memory/ ← physical directory
|
|
14
|
+
* public → ../../../memory/public (symlink to shared)
|
|
15
|
+
* shared → ../../../memory/shared (symlink to shared)
|
|
16
|
+
* users/ ← physical: public's per-user data
|
|
17
|
+
*
|
|
18
|
+
* Only `public/` and `shared/` are symlinked.
|
|
19
|
+
* `admin/` only exists in the admin agent workspace.
|
|
20
|
+
* `users/` is per-agent (each agent has its own user data).
|
|
21
|
+
*/
|
|
22
|
+
import fs from "node:fs/promises";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
25
|
+
const log = createSubsystemLogger("memory-layout");
|
|
26
|
+
/** Shared scope folders symlinked from each agent workspace. */
|
|
27
|
+
const SHARED_FOLDERS = ["public", "shared"];
|
|
28
|
+
/** Agent-specific folders that belong in per-agent memory, not at the shared root. */
|
|
29
|
+
const AGENT_SPECIFIC_FOLDERS = ["admin", "users", "groups"];
|
|
30
|
+
/** The relative symlink target from agents/{id}/memory/{scope} → ../../../memory/{scope} */
|
|
31
|
+
const SYMLINK_TARGET_PREFIX = "../../../memory";
|
|
32
|
+
async function exists(p) {
|
|
33
|
+
try {
|
|
34
|
+
await fs.access(p);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function isSymlink(p) {
|
|
42
|
+
try {
|
|
43
|
+
const stat = await fs.lstat(p);
|
|
44
|
+
return stat.isSymbolicLink();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function isDirectory(p) {
|
|
51
|
+
try {
|
|
52
|
+
const stat = await fs.lstat(p);
|
|
53
|
+
return stat.isDirectory();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function isEmptyDir(p) {
|
|
60
|
+
try {
|
|
61
|
+
const entries = await fs.readdir(p);
|
|
62
|
+
return entries.length === 0;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Move all files from src directory into dest directory (non-destructive merge).
|
|
70
|
+
* Only moves files/dirs that don't already exist in dest.
|
|
71
|
+
*/
|
|
72
|
+
async function mergeDir(src, dest) {
|
|
73
|
+
await fs.mkdir(dest, { recursive: true });
|
|
74
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const srcPath = path.join(src, entry.name);
|
|
77
|
+
const destPath = path.join(dest, entry.name);
|
|
78
|
+
if (await exists(destPath))
|
|
79
|
+
continue;
|
|
80
|
+
await fs.rename(srcPath, destPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Ensure the correct memory layout for a multi-agent workspace.
|
|
85
|
+
*
|
|
86
|
+
* - Creates the shared `memory/public` and `memory/shared` at workspace root
|
|
87
|
+
* - For each agent dir, ensures `memory/` is a physical directory with
|
|
88
|
+
* symlinks for `public/` and `shared/`, and physical dirs for agent-specific data
|
|
89
|
+
* - Migrates from the old full-symlink layout if found
|
|
90
|
+
* - Consolidates data from broken physical dirs into the shared location
|
|
91
|
+
*
|
|
92
|
+
* @param workspaceRoot The workspace root (e.g. ~/taskmaster)
|
|
93
|
+
* @param agentDirs Agent workspace directories (e.g. [~/taskmaster/agents/admin, ~/taskmaster/agents/public])
|
|
94
|
+
*/
|
|
95
|
+
export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
|
|
96
|
+
const sharedMemoryDir = path.join(workspaceRoot, "memory");
|
|
97
|
+
// Create shared canonical directories
|
|
98
|
+
for (const folder of SHARED_FOLDERS) {
|
|
99
|
+
await fs.mkdir(path.join(sharedMemoryDir, folder), { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
for (const { dir: agentDir, id: agentId } of agentDirs) {
|
|
102
|
+
const memoryPath = path.join(agentDir, "memory");
|
|
103
|
+
const isAdmin = agentId.toLowerCase() === "admin" || agentId.toLowerCase().endsWith("-admin");
|
|
104
|
+
// Case 1: Full memory symlink (old layout) — migrate to per-subfolder
|
|
105
|
+
if (await isSymlink(memoryPath)) {
|
|
106
|
+
log.info(`migrating full memory symlink to per-subfolder layout: ${agentDir}`);
|
|
107
|
+
// The symlink points to the shared memory dir — data is already there.
|
|
108
|
+
// Remove the full symlink and create a physical dir with per-subfolder symlinks.
|
|
109
|
+
await fs.unlink(memoryPath);
|
|
110
|
+
await fs.mkdir(memoryPath, { recursive: true });
|
|
111
|
+
// Data is already in the shared location, just create symlinks and physical dirs
|
|
112
|
+
}
|
|
113
|
+
// Ensure memory/ is a physical directory
|
|
114
|
+
if (!(await isDirectory(memoryPath))) {
|
|
115
|
+
await fs.mkdir(memoryPath, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
// For each shared folder: consolidate any physical data, then symlink
|
|
118
|
+
for (const folder of SHARED_FOLDERS) {
|
|
119
|
+
const subPath = path.join(memoryPath, folder);
|
|
120
|
+
const sharedPath = path.join(sharedMemoryDir, folder);
|
|
121
|
+
if (await isSymlink(subPath)) {
|
|
122
|
+
// Already a symlink — correct state
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (await isDirectory(subPath)) {
|
|
126
|
+
// Physical dir exists — merge data into shared location, then replace with symlink
|
|
127
|
+
if (!(await isEmptyDir(subPath))) {
|
|
128
|
+
log.info(`consolidating ${folder}/ data from ${agentDir} into shared location`);
|
|
129
|
+
await mergeDir(subPath, sharedPath);
|
|
130
|
+
}
|
|
131
|
+
await fs.rm(subPath, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
// Create symlink
|
|
134
|
+
const target = `${SYMLINK_TARGET_PREFIX}/${folder}`;
|
|
135
|
+
await fs.symlink(target, subPath).catch(() => {
|
|
136
|
+
/* ignore if race */
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Ensure physical dirs for agent-specific data
|
|
140
|
+
if (isAdmin) {
|
|
141
|
+
await fs.mkdir(path.join(memoryPath, "admin"), { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
await fs.mkdir(path.join(memoryPath, "users"), { recursive: true });
|
|
144
|
+
await fs.mkdir(path.join(memoryPath, "groups"), { recursive: true });
|
|
145
|
+
// Remove admin/ from non-admin agent workspaces (it should never exist there)
|
|
146
|
+
if (!isAdmin) {
|
|
147
|
+
const adminPath = path.join(memoryPath, "admin");
|
|
148
|
+
if ((await isDirectory(adminPath)) && !(await isSymlink(adminPath))) {
|
|
149
|
+
if (await isEmptyDir(adminPath)) {
|
|
150
|
+
await fs.rm(adminPath, { recursive: true }).catch(() => { });
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
log.warn(`non-admin agent has data in memory/admin/ — moving to admin agent`);
|
|
154
|
+
// Find admin agent dir and merge there
|
|
155
|
+
const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
|
|
156
|
+
if (adminAgent) {
|
|
157
|
+
const adminMemAdmin = path.join(adminAgent.dir, "memory", "admin");
|
|
158
|
+
await fs.mkdir(adminMemAdmin, { recursive: true });
|
|
159
|
+
await mergeDir(adminPath, adminMemAdmin);
|
|
160
|
+
}
|
|
161
|
+
await fs.rm(adminPath, { recursive: true }).catch(() => { });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Sweep orphaned agent-specific data from the shared root.
|
|
167
|
+
// After full-symlink migration, admin/, users/, groups/, notes/, and loose files
|
|
168
|
+
// remain at ~/taskmaster/memory/ but are no longer accessible via per-subfolder symlinks.
|
|
169
|
+
// Move them to the admin agent's workspace (most privileged, owns historical data).
|
|
170
|
+
await migrateOrphanedSharedData(sharedMemoryDir, agentDirs);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Move agent-specific data that was left at the shared memory root
|
|
174
|
+
* (from the old full-symlink layout) into the admin agent's workspace.
|
|
175
|
+
*
|
|
176
|
+
* Only `public/` and `shared/` belong at the shared root.
|
|
177
|
+
* Everything else is orphaned and needs to move to a per-agent location.
|
|
178
|
+
*/
|
|
179
|
+
async function migrateOrphanedSharedData(sharedMemoryDir, agentDirs) {
|
|
180
|
+
const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
|
|
181
|
+
if (!adminAgent)
|
|
182
|
+
return; // No admin agent — nothing to migrate to
|
|
183
|
+
const adminMemoryPath = path.join(adminAgent.dir, "memory");
|
|
184
|
+
let entries;
|
|
185
|
+
try {
|
|
186
|
+
entries = await fs.readdir(sharedMemoryDir);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return; // Shared dir doesn't exist
|
|
190
|
+
}
|
|
191
|
+
const sharedSet = new Set(SHARED_FOLDERS);
|
|
192
|
+
for (const name of entries) {
|
|
193
|
+
// Skip shared folders — they belong here
|
|
194
|
+
if (sharedSet.has(name))
|
|
195
|
+
continue;
|
|
196
|
+
// Skip .DS_Store and other hidden files
|
|
197
|
+
if (name.startsWith("."))
|
|
198
|
+
continue;
|
|
199
|
+
const srcPath = path.join(sharedMemoryDir, name);
|
|
200
|
+
if (await isDirectory(srcPath)) {
|
|
201
|
+
const destPath = path.join(adminMemoryPath, name);
|
|
202
|
+
if (await isEmptyDir(srcPath)) {
|
|
203
|
+
// Empty orphan dir — just remove it
|
|
204
|
+
await fs.rm(srcPath, { recursive: true }).catch(() => { });
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
// Merge into admin agent's workspace
|
|
208
|
+
log.info(`migrating orphaned ${name}/ from shared root to admin agent`);
|
|
209
|
+
await mergeDir(srcPath, destPath);
|
|
210
|
+
// Remove source if now empty
|
|
211
|
+
if (await isEmptyDir(srcPath)) {
|
|
212
|
+
await fs.rm(srcPath, { recursive: true }).catch(() => { });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (name.endsWith(".md")) {
|
|
216
|
+
// Loose .md files — move to admin agent's memory root
|
|
217
|
+
const destPath = path.join(adminMemoryPath, name);
|
|
218
|
+
if (!(await exists(destPath))) {
|
|
219
|
+
log.info(`migrating orphaned ${name} from shared root to admin agent`);
|
|
220
|
+
await fs.rename(srcPath, destPath);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|