@dexto/core 1.1.4
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/LICENSE +44 -0
- package/dist/chunk-D62MHQBE.js +2203 -0
- package/dist/chunk-F2QFAECT.js +87 -0
- package/dist/chunk-FCJVTIBV.js +535 -0
- package/dist/chunk-J6AXCN3H.js +1268 -0
- package/dist/chunk-MVKLS3LM.js +43 -0
- package/dist/chunk-PI6XFMEW.js +141 -0
- package/dist/chunk-TPERKLLN.js +75 -0
- package/dist/chunk-XFQLRBHE.js +122 -0
- package/dist/errors-ZZ4Z3FKB.js +10 -0
- package/dist/index.browser.cjs +250 -0
- package/dist/index.browser.d.cts +379 -0
- package/dist/index.browser.d.ts +379 -0
- package/dist/index.browser.js +217 -0
- package/dist/index.cjs +15283 -0
- package/dist/index.d.cts +6842 -0
- package/dist/index.d.ts +6842 -0
- package/dist/index.js +9914 -0
- package/dist/loader-HBNEYPQZ.js +20 -0
- package/dist/path-TP7WBDED.js +21 -0
- package/dist/postgres-backend-WMWS7RAT.js +216 -0
- package/dist/redis-backend-BNLN3XHX.js +169 -0
- package/dist/registry-Z4DFXODW.js +14 -0
- package/dist/sqlite-backend-AR6XNK2Q.js +248 -0
- package/package.json +61 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DextoRuntimeError
|
|
3
|
+
} from "./chunk-TPERKLLN.js";
|
|
4
|
+
import {
|
|
5
|
+
init_esm_shims
|
|
6
|
+
} from "./chunk-MVKLS3LM.js";
|
|
7
|
+
|
|
8
|
+
// src/storage/errors.ts
|
|
9
|
+
init_esm_shims();
|
|
10
|
+
var StorageError = class {
|
|
11
|
+
/**
|
|
12
|
+
* Connection failed error
|
|
13
|
+
*/
|
|
14
|
+
static connectionFailed(reason, config) {
|
|
15
|
+
return new DextoRuntimeError(
|
|
16
|
+
"storage_connection_failed" /* CONNECTION_FAILED */,
|
|
17
|
+
"storage" /* STORAGE */,
|
|
18
|
+
"third_party" /* THIRD_PARTY */,
|
|
19
|
+
`Storage connection failed: ${reason}`,
|
|
20
|
+
{ reason, config }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Backend not connected error
|
|
25
|
+
*/
|
|
26
|
+
static notConnected(backendType) {
|
|
27
|
+
return new DextoRuntimeError(
|
|
28
|
+
"storage_connection_failed" /* CONNECTION_FAILED */,
|
|
29
|
+
"storage" /* STORAGE */,
|
|
30
|
+
"system" /* SYSTEM */,
|
|
31
|
+
`${backendType} not connected`,
|
|
32
|
+
{ backendType }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read operation failed
|
|
37
|
+
*/
|
|
38
|
+
static readFailed(operation, reason, details) {
|
|
39
|
+
return new DextoRuntimeError(
|
|
40
|
+
"storage_read_failed" /* READ_FAILED */,
|
|
41
|
+
"storage" /* STORAGE */,
|
|
42
|
+
"system" /* SYSTEM */,
|
|
43
|
+
`Storage read failed for ${operation}: ${reason}`,
|
|
44
|
+
{ operation, reason, ...details }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write operation failed
|
|
49
|
+
*/
|
|
50
|
+
static writeFailed(operation, reason, details) {
|
|
51
|
+
return new DextoRuntimeError(
|
|
52
|
+
"storage_write_failed" /* WRITE_FAILED */,
|
|
53
|
+
"storage" /* STORAGE */,
|
|
54
|
+
"system" /* SYSTEM */,
|
|
55
|
+
`Storage write failed for ${operation}: ${reason}`,
|
|
56
|
+
{ operation, reason, ...details }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Delete operation failed
|
|
61
|
+
*/
|
|
62
|
+
static deleteFailed(operation, reason, details) {
|
|
63
|
+
return new DextoRuntimeError(
|
|
64
|
+
"storage_delete_failed" /* DELETE_FAILED */,
|
|
65
|
+
"storage" /* STORAGE */,
|
|
66
|
+
"system" /* SYSTEM */,
|
|
67
|
+
`Storage delete failed for ${operation}: ${reason}`,
|
|
68
|
+
{ operation, reason, ...details }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Migration failed error
|
|
73
|
+
*/
|
|
74
|
+
static migrationFailed(reason, details) {
|
|
75
|
+
return new DextoRuntimeError(
|
|
76
|
+
"storage_migration_failed" /* MIGRATION_FAILED */,
|
|
77
|
+
"storage" /* STORAGE */,
|
|
78
|
+
"system" /* SYSTEM */,
|
|
79
|
+
`Database migration failed: ${reason}`,
|
|
80
|
+
{ reason, ...details }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
StorageError
|
|
87
|
+
};
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadGlobalPreferences
|
|
3
|
+
} from "./chunk-J6AXCN3H.js";
|
|
4
|
+
import {
|
|
5
|
+
RegistryError
|
|
6
|
+
} from "./chunk-PI6XFMEW.js";
|
|
7
|
+
import {
|
|
8
|
+
copyDirectory,
|
|
9
|
+
getDextoGlobalPath,
|
|
10
|
+
logger,
|
|
11
|
+
resolveBundledScript
|
|
12
|
+
} from "./chunk-D62MHQBE.js";
|
|
13
|
+
import {
|
|
14
|
+
DextoRuntimeError
|
|
15
|
+
} from "./chunk-TPERKLLN.js";
|
|
16
|
+
import {
|
|
17
|
+
init_esm_shims
|
|
18
|
+
} from "./chunk-MVKLS3LM.js";
|
|
19
|
+
|
|
20
|
+
// src/agent/registry/registry.ts
|
|
21
|
+
init_esm_shims();
|
|
22
|
+
import { existsSync, readFileSync } from "fs";
|
|
23
|
+
import { promises as fs2 } from "fs";
|
|
24
|
+
import path2 from "path";
|
|
25
|
+
|
|
26
|
+
// src/config/writer.ts
|
|
27
|
+
init_esm_shims();
|
|
28
|
+
import { promises as fs } from "fs";
|
|
29
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
30
|
+
import * as path from "path";
|
|
31
|
+
|
|
32
|
+
// src/config/errors.ts
|
|
33
|
+
init_esm_shims();
|
|
34
|
+
var ConfigError = class {
|
|
35
|
+
// File operation errors
|
|
36
|
+
static fileNotFound(configPath) {
|
|
37
|
+
return new DextoRuntimeError(
|
|
38
|
+
"config_file_not_found" /* FILE_NOT_FOUND */,
|
|
39
|
+
"config" /* CONFIG */,
|
|
40
|
+
"user" /* USER */,
|
|
41
|
+
`Configuration file not found: ${configPath}`,
|
|
42
|
+
{ configPath },
|
|
43
|
+
"Ensure the configuration file exists at the specified path"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
static fileReadError(configPath, cause) {
|
|
47
|
+
return new DextoRuntimeError(
|
|
48
|
+
"config_file_read_error" /* FILE_READ_ERROR */,
|
|
49
|
+
"config" /* CONFIG */,
|
|
50
|
+
"system" /* SYSTEM */,
|
|
51
|
+
`Failed to read configuration file: ${cause}`,
|
|
52
|
+
{ configPath, cause },
|
|
53
|
+
"Check file permissions and ensure the file is not corrupted"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
static fileWriteError(configPath, cause) {
|
|
57
|
+
return new DextoRuntimeError(
|
|
58
|
+
"config_file_write_error" /* FILE_WRITE_ERROR */,
|
|
59
|
+
"config" /* CONFIG */,
|
|
60
|
+
"system" /* SYSTEM */,
|
|
61
|
+
`Failed to write configuration file '${configPath}': ${cause}`,
|
|
62
|
+
{ configPath, cause },
|
|
63
|
+
"Check file permissions and available disk space"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
// Parsing errors
|
|
67
|
+
static parseError(configPath, cause) {
|
|
68
|
+
return new DextoRuntimeError(
|
|
69
|
+
"config_parse_error" /* PARSE_ERROR */,
|
|
70
|
+
"config" /* CONFIG */,
|
|
71
|
+
"user" /* USER */,
|
|
72
|
+
`Failed to parse configuration file: ${cause}`,
|
|
73
|
+
{ configPath, cause },
|
|
74
|
+
"Ensure the configuration file contains valid YAML syntax"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
// Resolution errors
|
|
78
|
+
static noProjectDefault(projectPath) {
|
|
79
|
+
return new DextoRuntimeError(
|
|
80
|
+
"config_no_project_default" /* NO_PROJECT_DEFAULT */,
|
|
81
|
+
"config" /* CONFIG */,
|
|
82
|
+
"user" /* USER */,
|
|
83
|
+
`No project default-agent.yml found and no global preferences configured.
|
|
84
|
+
Either create default-agent.yml in your project root (${projectPath}) or run \`dexto setup\` to configure preferences.`,
|
|
85
|
+
{ projectPath },
|
|
86
|
+
"Run `dexto setup` or create a project-specific agent config"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
static noGlobalPreferences() {
|
|
90
|
+
return new DextoRuntimeError(
|
|
91
|
+
"config_no_global_preferences" /* NO_GLOBAL_PREFERENCES */,
|
|
92
|
+
"config" /* CONFIG */,
|
|
93
|
+
"user" /* USER */,
|
|
94
|
+
`No global preferences found. Run \`dexto setup\` to get started.`,
|
|
95
|
+
{},
|
|
96
|
+
"Run `dexto setup` to configure your AI preferences"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
static setupIncomplete() {
|
|
100
|
+
return new DextoRuntimeError(
|
|
101
|
+
"config_setup_incomplete" /* SETUP_INCOMPLETE */,
|
|
102
|
+
"config" /* CONFIG */,
|
|
103
|
+
"user" /* USER */,
|
|
104
|
+
`Global preferences setup is incomplete. Run \`dexto setup\` to complete.`,
|
|
105
|
+
{},
|
|
106
|
+
"Run `dexto setup` to complete your configuration"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
static bundledNotFound(bundledPath) {
|
|
110
|
+
return new DextoRuntimeError(
|
|
111
|
+
"config_bundled_not_found" /* BUNDLED_NOT_FOUND */,
|
|
112
|
+
"config" /* CONFIG */,
|
|
113
|
+
"not_found" /* NOT_FOUND */,
|
|
114
|
+
`Bundled default agent not found: ${bundledPath}. Run npm run build first.`,
|
|
115
|
+
{ path: bundledPath },
|
|
116
|
+
"Run `npm run build` to build the bundled agents"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
static unknownContext(context) {
|
|
120
|
+
return new DextoRuntimeError(
|
|
121
|
+
"config_unknown_context" /* UNKNOWN_CONTEXT */,
|
|
122
|
+
"config" /* CONFIG */,
|
|
123
|
+
"system" /* SYSTEM */,
|
|
124
|
+
`Unknown execution context: ${context}`,
|
|
125
|
+
{ context },
|
|
126
|
+
"This is an internal error - please report it"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/config/writer.ts
|
|
132
|
+
async function writeConfigFile(configPath, config) {
|
|
133
|
+
const absolutePath = path.resolve(configPath);
|
|
134
|
+
try {
|
|
135
|
+
const yamlContent = stringifyYaml(config, { indent: 2 });
|
|
136
|
+
await fs.writeFile(absolutePath, yamlContent, "utf-8");
|
|
137
|
+
logger.debug(`Wrote dexto config to: ${absolutePath}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw ConfigError.fileWriteError(
|
|
140
|
+
absolutePath,
|
|
141
|
+
error instanceof Error ? error.message : String(error)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function writeLLMPreferences(configPath, preferences, overrides) {
|
|
146
|
+
logger.debug(`Writing LLM preferences to: ${configPath}`, {
|
|
147
|
+
provider: overrides?.provider ?? preferences.llm.provider,
|
|
148
|
+
model: overrides?.model ?? preferences.llm.model,
|
|
149
|
+
hasApiKeyOverride: Boolean(overrides?.apiKey),
|
|
150
|
+
hasPreferenceApiKey: Boolean(preferences.llm.apiKey)
|
|
151
|
+
});
|
|
152
|
+
logger.debug(`Reading config file: ${configPath}`);
|
|
153
|
+
let fileContent;
|
|
154
|
+
try {
|
|
155
|
+
fileContent = await fs.readFile(configPath, "utf-8");
|
|
156
|
+
logger.debug(`Successfully read config file (${fileContent.length} chars)`);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
logger.error(`Failed to read config file: ${configPath}`, { error });
|
|
159
|
+
throw ConfigError.fileReadError(
|
|
160
|
+
configPath,
|
|
161
|
+
error instanceof Error ? error.message : String(error)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
let config;
|
|
165
|
+
try {
|
|
166
|
+
config = parseYaml(fileContent);
|
|
167
|
+
logger.debug(`Successfully parsed YAML config`, {
|
|
168
|
+
hasLlmSection: Boolean(config.llm),
|
|
169
|
+
existingProvider: config.llm?.provider,
|
|
170
|
+
existingModel: config.llm?.model
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.error(`Failed to parse YAML config: ${configPath}`, { error });
|
|
174
|
+
throw ConfigError.parseError(
|
|
175
|
+
configPath,
|
|
176
|
+
error instanceof Error ? error.message : String(error)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const provider = overrides?.provider ?? preferences.llm.provider;
|
|
180
|
+
const model = overrides?.model ?? preferences.llm.model;
|
|
181
|
+
const apiKey = overrides?.apiKey ?? preferences.llm.apiKey;
|
|
182
|
+
logger.debug(`Applying LLM preferences`, {
|
|
183
|
+
finalProvider: provider,
|
|
184
|
+
finalModel: model,
|
|
185
|
+
hasApiKey: Boolean(apiKey),
|
|
186
|
+
source: overrides ? "CLI overrides + preferences" : "preferences only"
|
|
187
|
+
});
|
|
188
|
+
config.llm = {
|
|
189
|
+
...config.llm,
|
|
190
|
+
// Preserve temperature, router, maxTokens, etc.
|
|
191
|
+
provider,
|
|
192
|
+
// Write user preference
|
|
193
|
+
model,
|
|
194
|
+
// Write user preference
|
|
195
|
+
apiKey
|
|
196
|
+
// Write user preference
|
|
197
|
+
};
|
|
198
|
+
await writeConfigFile(configPath, config);
|
|
199
|
+
logger.info(`\u2713 Applied preferences to: ${path.basename(configPath)} (${provider}/${model})`);
|
|
200
|
+
}
|
|
201
|
+
async function writePreferencesToAgent(installedPath, preferences, overrides) {
|
|
202
|
+
let stat;
|
|
203
|
+
try {
|
|
204
|
+
stat = await fs.stat(installedPath);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw ConfigError.fileReadError(
|
|
207
|
+
installedPath,
|
|
208
|
+
error instanceof Error ? error.message : String(error)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (stat.isFile()) {
|
|
212
|
+
if (installedPath.endsWith(".yml") || installedPath.endsWith(".yaml")) {
|
|
213
|
+
await writeLLMPreferences(installedPath, preferences, overrides);
|
|
214
|
+
logger.info(`\u2713 Applied preferences to: ${path.basename(installedPath)}`, null, "green");
|
|
215
|
+
} else {
|
|
216
|
+
logger.warn(`Skipping non-YAML file: ${installedPath}`, null, "yellow");
|
|
217
|
+
}
|
|
218
|
+
} else if (stat.isDirectory()) {
|
|
219
|
+
await writePreferencesToDirectory(installedPath, preferences, overrides);
|
|
220
|
+
} else {
|
|
221
|
+
throw ConfigError.fileReadError(installedPath, "Path is neither a file nor directory");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function writePreferencesToDirectory(installedDir, preferences, overrides) {
|
|
225
|
+
const configFiles = await findAgentConfigFiles(installedDir);
|
|
226
|
+
if (configFiles.length === 0) {
|
|
227
|
+
logger.warn(`No YAML config files found in: ${installedDir}`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let successCount = 0;
|
|
231
|
+
const oldProvider = preferences.llm.provider;
|
|
232
|
+
const oldModel = preferences.llm.model;
|
|
233
|
+
const newProvider = overrides?.provider ?? preferences.llm.provider;
|
|
234
|
+
const newModel = overrides?.model ?? preferences.llm.model;
|
|
235
|
+
for (const configPath of configFiles) {
|
|
236
|
+
try {
|
|
237
|
+
await writeLLMPreferences(configPath, preferences, overrides);
|
|
238
|
+
logger.debug(`Applied preferences to: ${path.relative(installedDir, configPath)}`);
|
|
239
|
+
successCount++;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.warn(
|
|
242
|
+
`Failed to write preferences to ${configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
logger.info(
|
|
247
|
+
`\u2713 Applied preferences to ${successCount}/${configFiles.length} config files (${oldProvider}\u2192${newProvider}, ${oldModel}\u2192${newModel})`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async function findAgentConfigFiles(dir) {
|
|
251
|
+
const configFiles = [];
|
|
252
|
+
async function walkDir(currentDir) {
|
|
253
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
254
|
+
for (const entry of entries) {
|
|
255
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
256
|
+
if (entry.isDirectory()) {
|
|
257
|
+
if (!["docs", "data"].includes(entry.name)) {
|
|
258
|
+
await walkDir(fullPath);
|
|
259
|
+
}
|
|
260
|
+
} else if (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml")) {
|
|
261
|
+
configFiles.push(fullPath);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
await walkDir(dir);
|
|
266
|
+
return configFiles;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/agent/registry/types.ts
|
|
270
|
+
init_esm_shims();
|
|
271
|
+
import { z } from "zod";
|
|
272
|
+
var AgentRegistryEntrySchema = z.object({
|
|
273
|
+
description: z.string(),
|
|
274
|
+
author: z.string(),
|
|
275
|
+
tags: z.array(z.string()),
|
|
276
|
+
source: z.string(),
|
|
277
|
+
main: z.string().optional()
|
|
278
|
+
}).strict();
|
|
279
|
+
var RegistrySchema = z.object({
|
|
280
|
+
version: z.string(),
|
|
281
|
+
agents: z.record(z.string(), AgentRegistryEntrySchema)
|
|
282
|
+
}).strict();
|
|
283
|
+
|
|
284
|
+
// src/agent/registry/registry.ts
|
|
285
|
+
var cachedRegistry = null;
|
|
286
|
+
var LocalAgentRegistry = class {
|
|
287
|
+
_registry = null;
|
|
288
|
+
/**
|
|
289
|
+
* Lazy load registry from JSON file
|
|
290
|
+
*/
|
|
291
|
+
getRegistry() {
|
|
292
|
+
if (this._registry === null) {
|
|
293
|
+
this._registry = this.loadRegistry();
|
|
294
|
+
}
|
|
295
|
+
return this._registry;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Load registry from bundled JSON file
|
|
299
|
+
* Uses fail-fast approach - throws RegistryError for any loading issues
|
|
300
|
+
*/
|
|
301
|
+
loadRegistry() {
|
|
302
|
+
let jsonPath;
|
|
303
|
+
try {
|
|
304
|
+
jsonPath = resolveBundledScript("agents/agent-registry.json");
|
|
305
|
+
} catch (_error) {
|
|
306
|
+
throw RegistryError.registryNotFound(
|
|
307
|
+
"agents/agent-registry.json (bundle resolution failed)"
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (!existsSync(jsonPath)) {
|
|
311
|
+
throw RegistryError.registryNotFound(jsonPath);
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const jsonData = readFileSync(jsonPath, "utf-8");
|
|
315
|
+
const rawRegistry = JSON.parse(jsonData);
|
|
316
|
+
return RegistrySchema.parse(rawRegistry);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
throw RegistryError.registryParseError(
|
|
319
|
+
jsonPath,
|
|
320
|
+
error instanceof Error ? error.message : String(error)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Check if agent exists in registry
|
|
326
|
+
*/
|
|
327
|
+
hasAgent(name) {
|
|
328
|
+
const registry = this.getRegistry();
|
|
329
|
+
return name in registry.agents;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get available agents with their metadata from registry
|
|
333
|
+
*/
|
|
334
|
+
getAvailableAgents() {
|
|
335
|
+
const registry = this.getRegistry();
|
|
336
|
+
return registry.agents;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resolve main config file for installed agent
|
|
340
|
+
* Handles both directory agents (with main field) and single-file agents
|
|
341
|
+
*/
|
|
342
|
+
resolveMainConfig(agentDir, agentName) {
|
|
343
|
+
const registry = this.getRegistry();
|
|
344
|
+
const agentData = registry.agents[agentName];
|
|
345
|
+
if (!agentData) {
|
|
346
|
+
const available = Object.keys(registry.agents);
|
|
347
|
+
throw RegistryError.agentNotFound(agentName, available);
|
|
348
|
+
}
|
|
349
|
+
if (agentData.source.endsWith("/")) {
|
|
350
|
+
if (!agentData.main) {
|
|
351
|
+
throw RegistryError.agentInvalidEntry(
|
|
352
|
+
agentName,
|
|
353
|
+
"directory entry missing main field"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const mainConfigPath = path2.join(agentDir, agentData.main);
|
|
357
|
+
if (!existsSync(mainConfigPath)) {
|
|
358
|
+
throw RegistryError.mainConfigMissing(agentName, mainConfigPath);
|
|
359
|
+
}
|
|
360
|
+
return mainConfigPath;
|
|
361
|
+
} else {
|
|
362
|
+
const filename = path2.basename(agentData.source);
|
|
363
|
+
const configPath = path2.join(agentDir, filename);
|
|
364
|
+
if (!existsSync(configPath)) {
|
|
365
|
+
throw RegistryError.configNotFound(configPath);
|
|
366
|
+
}
|
|
367
|
+
return configPath;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Install agent atomically using temp + rename pattern
|
|
372
|
+
* @param agentName Name of the agent to install
|
|
373
|
+
* @param injectPreferences Whether to inject global preferences into installed agent (default: true)
|
|
374
|
+
*/
|
|
375
|
+
async installAgent(agentName, injectPreferences = true) {
|
|
376
|
+
logger.info(`Installing agent: ${agentName}`);
|
|
377
|
+
const registry = this.getRegistry();
|
|
378
|
+
const agentData = registry.agents[agentName];
|
|
379
|
+
if (!agentData) {
|
|
380
|
+
const available = Object.keys(registry.agents);
|
|
381
|
+
throw RegistryError.agentNotFound(agentName, available);
|
|
382
|
+
}
|
|
383
|
+
const globalAgentsDir = getDextoGlobalPath("agents");
|
|
384
|
+
const targetDir = path2.join(globalAgentsDir, agentName);
|
|
385
|
+
if (existsSync(targetDir)) {
|
|
386
|
+
logger.info(`Agent '${agentName}' already installed`);
|
|
387
|
+
return this.resolveMainConfig(targetDir, agentName);
|
|
388
|
+
}
|
|
389
|
+
await fs2.mkdir(globalAgentsDir, { recursive: true });
|
|
390
|
+
const sourcePath = resolveBundledScript(`agents/${agentData.source}`);
|
|
391
|
+
const tempDir = `${targetDir}.tmp.${Date.now()}`;
|
|
392
|
+
try {
|
|
393
|
+
if (agentData.source.endsWith("/")) {
|
|
394
|
+
await copyDirectory(sourcePath, tempDir);
|
|
395
|
+
} else {
|
|
396
|
+
await fs2.mkdir(tempDir, { recursive: true });
|
|
397
|
+
const targetFile = path2.join(tempDir, path2.basename(sourcePath));
|
|
398
|
+
await fs2.copyFile(sourcePath, targetFile);
|
|
399
|
+
}
|
|
400
|
+
const mainConfigPath = this.resolveMainConfig(tempDir, agentName);
|
|
401
|
+
if (!existsSync(mainConfigPath)) {
|
|
402
|
+
throw RegistryError.installationValidationFailed(agentName, mainConfigPath);
|
|
403
|
+
}
|
|
404
|
+
await fs2.rename(tempDir, targetDir);
|
|
405
|
+
logger.info(`\u2713 Installed agent '${agentName}' to ${targetDir}`);
|
|
406
|
+
if (injectPreferences) {
|
|
407
|
+
try {
|
|
408
|
+
const preferences = await loadGlobalPreferences();
|
|
409
|
+
await writePreferencesToAgent(targetDir, preferences);
|
|
410
|
+
logger.info(`\u2713 Applied global preferences to installed agent '${agentName}'`);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
logger.warn(
|
|
413
|
+
`Failed to inject preferences to '${agentName}': ${error instanceof Error ? error.message : String(error)}`
|
|
414
|
+
);
|
|
415
|
+
console.log(
|
|
416
|
+
`\u26A0\uFE0F Warning: Could not apply preferences to '${agentName}' - agent will use bundled settings`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
logger.info(
|
|
421
|
+
`Skipped preference injection for '${agentName}' (injectPreferences=false)`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
return this.resolveMainConfig(targetDir, agentName);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
try {
|
|
427
|
+
if (existsSync(tempDir)) {
|
|
428
|
+
await fs2.rm(tempDir, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
} catch (cleanupError) {
|
|
431
|
+
logger.error(
|
|
432
|
+
`Failed to clean up temp directory: ${cleanupError}. Skipping cleanup...`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
throw RegistryError.installationFailed(
|
|
436
|
+
agentName,
|
|
437
|
+
error instanceof Error ? error.message : String(error)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Resolve a registry agent name to a config path
|
|
443
|
+
* NOTE: Only handles registry names, not file paths (routing done in loadAgentConfig)
|
|
444
|
+
* Handles installing agent if needed
|
|
445
|
+
* @param agentName Name of the agent to resolve
|
|
446
|
+
* @param autoInstall Whether to automatically install missing agents from registry (default: true)
|
|
447
|
+
* @param injectPreferences Whether to inject preferences during auto-installation (default: true)
|
|
448
|
+
*/
|
|
449
|
+
async resolveAgent(agentName, autoInstall = true, injectPreferences = true) {
|
|
450
|
+
logger.debug(`Resolving registry agent: ${agentName}`);
|
|
451
|
+
const globalAgentsDir = getDextoGlobalPath("agents");
|
|
452
|
+
const installedPath = path2.join(globalAgentsDir, agentName);
|
|
453
|
+
if (existsSync(installedPath)) {
|
|
454
|
+
const mainConfig = this.resolveMainConfig(installedPath, agentName);
|
|
455
|
+
logger.debug(`Resolved installed agent '${agentName}' to: ${mainConfig}`);
|
|
456
|
+
return mainConfig;
|
|
457
|
+
}
|
|
458
|
+
logger.debug(`Agent '${agentName}' not found in installed path: ${installedPath}`);
|
|
459
|
+
if (this.hasAgent(agentName)) {
|
|
460
|
+
if (autoInstall) {
|
|
461
|
+
logger.info(`Installing agent '${agentName}' from registry...`);
|
|
462
|
+
return await this.installAgent(agentName, injectPreferences);
|
|
463
|
+
} else {
|
|
464
|
+
const registry2 = this.getRegistry();
|
|
465
|
+
const available2 = Object.keys(registry2.agents);
|
|
466
|
+
throw RegistryError.agentNotInstalledAutoInstallDisabled(agentName, available2);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const registry = this.getRegistry();
|
|
470
|
+
const available = Object.keys(registry.agents);
|
|
471
|
+
throw RegistryError.agentNotFound(agentName, available);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get list of currently installed agents
|
|
475
|
+
*/
|
|
476
|
+
async getInstalledAgents() {
|
|
477
|
+
const globalAgentsDir = getDextoGlobalPath("agents");
|
|
478
|
+
if (!existsSync(globalAgentsDir)) {
|
|
479
|
+
return [];
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const entries = await fs2.readdir(globalAgentsDir, { withFileTypes: true });
|
|
483
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !name.startsWith(".tmp") && !name.includes(".tmp."));
|
|
484
|
+
} catch (error) {
|
|
485
|
+
logger.error(`Failed to read installed agents directory: ${error}`);
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Check if an agent is safe to uninstall (not the default-agent which is critical)
|
|
491
|
+
*/
|
|
492
|
+
isAgentSafeToUninstall(agentName) {
|
|
493
|
+
return agentName !== "default-agent";
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Uninstall an agent by removing its directory
|
|
497
|
+
* @param agentName Name of the agent to uninstall
|
|
498
|
+
* @param force Whether to force uninstall even if agent is protected (default: false)
|
|
499
|
+
*/
|
|
500
|
+
async uninstallAgent(agentName, force = false) {
|
|
501
|
+
const globalAgentsDir = getDextoGlobalPath("agents");
|
|
502
|
+
const agentDir = path2.join(globalAgentsDir, agentName);
|
|
503
|
+
logger.info(`Uninstalling agent: ${agentName} from ${agentDir}`);
|
|
504
|
+
if (!existsSync(agentDir)) {
|
|
505
|
+
throw RegistryError.agentNotInstalled(agentName);
|
|
506
|
+
}
|
|
507
|
+
if (!force && !this.isAgentSafeToUninstall(agentName)) {
|
|
508
|
+
throw RegistryError.agentProtected(agentName);
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
await fs2.rm(agentDir, { recursive: true, force: true });
|
|
512
|
+
logger.info(`\u2713 Removed agent '${agentName}' from ${agentDir}`);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw RegistryError.uninstallationFailed(
|
|
515
|
+
agentName,
|
|
516
|
+
error instanceof Error ? error.message : String(error)
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
function getAgentRegistry() {
|
|
522
|
+
if (cachedRegistry === null) {
|
|
523
|
+
cachedRegistry = new LocalAgentRegistry();
|
|
524
|
+
}
|
|
525
|
+
return cachedRegistry;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export {
|
|
529
|
+
ConfigError,
|
|
530
|
+
writeConfigFile,
|
|
531
|
+
writeLLMPreferences,
|
|
532
|
+
writePreferencesToAgent,
|
|
533
|
+
LocalAgentRegistry,
|
|
534
|
+
getAgentRegistry
|
|
535
|
+
};
|