@elizaos/agent 2.0.0-alpha.421 → 2.0.0-alpha.425
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/package.json +4 -4
- package/packages/agent/src/api/accounts-routes.d.ts +11 -3
- package/packages/agent/src/api/accounts-routes.d.ts.map +1 -1
- package/packages/agent/src/api/accounts-routes.js +70 -123
- package/packages/agent/src/api/subscription-routes.js +27 -23
- package/packages/agent/src/runtime/plugin-role-gating.js +4 -3
- package/packages/app-core/src/api/client-agent.d.ts +63 -1
- package/packages/app-core/src/api/client-agent.d.ts.map +1 -1
- package/packages/app-core/src/api/client-agent.js +51 -0
- package/packages/app-core/src/components/accounts/AccountCard.d.ts +30 -0
- package/packages/app-core/src/components/accounts/AccountCard.d.ts.map +1 -0
- package/packages/app-core/src/components/accounts/AccountCard.js +183 -0
- package/packages/app-core/src/components/accounts/AccountList.d.ts +15 -0
- package/packages/app-core/src/components/accounts/AccountList.d.ts.map +1 -0
- package/packages/app-core/src/components/accounts/AccountList.js +80 -0
- package/packages/app-core/src/components/accounts/AddAccountDialog.d.ts +33 -0
- package/packages/app-core/src/components/accounts/AddAccountDialog.d.ts.map +1 -0
- package/packages/app-core/src/components/accounts/AddAccountDialog.js +277 -0
- package/packages/app-core/src/components/accounts/RotationStrategyPicker.d.ts +16 -0
- package/packages/app-core/src/components/accounts/RotationStrategyPicker.d.ts.map +1 -0
- package/packages/app-core/src/components/accounts/RotationStrategyPicker.js +50 -0
- package/packages/app-core/src/components/pages/PluginsView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/PluginsView.js +3 -34
- package/packages/app-core/src/components/settings/ProviderSwitcher.d.ts.map +1 -1
- package/packages/app-core/src/components/settings/ProviderSwitcher.js +2 -1
- package/packages/app-core/src/hooks/useAccounts.d.ts +41 -0
- package/packages/app-core/src/hooks/useAccounts.d.ts.map +1 -0
- package/packages/app-core/src/hooks/useAccounts.js +250 -0
- package/packages/app-core/src/i18n/locales/en.json +31 -31
- package/packages/app-core/src/i18n/locales/es.json +31 -31
- package/packages/app-core/src/i18n/locales/ko.json +31 -31
- package/packages/app-core/src/i18n/locales/pt.json +31 -31
- package/packages/app-core/src/i18n/locales/tl.json +31 -31
- package/packages/app-core/src/i18n/locales/vi.json +31 -31
- package/packages/app-core/src/i18n/locales/zh-CN.json +31 -31
- package/packages/typescript/src/features/basic-capabilities/index.d.ts +1 -0
- package/packages/typescript/src/features/basic-capabilities/index.d.ts.map +1 -1
- package/packages/typescript/src/features/basic-capabilities/index.js +4 -0
- package/packages/typescript/src/features/index.d.ts.map +1 -1
- package/packages/typescript/src/features/index.js +2 -7
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.d.ts +14 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/core-status.js +48 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.d.ts +32 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/create.js +502 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.d.ts +16 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.js +47 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.d.ts +18 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.js +59 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.d.ts +14 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.js +31 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.d.ts +14 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list.js +51 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.d.ts +15 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/reinject.js +43 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.d.ts +15 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/search.js +52 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.d.ts +15 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/sync.js +48 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin.d.ts +25 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/actions/plugin.js +439 -0
- package/packages/typescript/src/features/plugin-manager/index.d.ts +2 -0
- package/packages/typescript/src/features/plugin-manager/index.d.ts.map +1 -1
- package/packages/typescript/src/features/plugin-manager/index.js +7 -10
- package/packages/typescript/src/features/plugin-manager/security.d.ts +33 -0
- package/packages/typescript/src/features/plugin-manager/security.d.ts.map +1 -0
- package/packages/typescript/src/features/plugin-manager/security.js +80 -0
- package/packages/app-core/src/runtime/plugin-manager-guard.d.ts +0 -12
- package/packages/app-core/src/runtime/plugin-manager-guard.d.ts.map +0 -1
- package/packages/app-core/src/runtime/plugin-manager-guard.js +0 -82
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logger } from "../../../../logger.js";
|
|
4
|
+
export const PLUGIN_CREATE_INTENT_TAG = "plugin-create-intent";
|
|
5
|
+
const TEMPLATE_CANDIDATES = [
|
|
6
|
+
"eliza/templates/min-plugin",
|
|
7
|
+
"templates/min-plugin",
|
|
8
|
+
];
|
|
9
|
+
const PLUGINS_DIR_CANDIDATES = ["eliza/plugins", "plugins"];
|
|
10
|
+
const NAME_PLACEHOLDER = "__PLUGIN_NAME__";
|
|
11
|
+
const DISPLAY_NAME_PLACEHOLDER = "__PLUGIN_DISPLAY_NAME__";
|
|
12
|
+
const CHOICE_RE = /^(new|edit-\d+|cancel)$/i;
|
|
13
|
+
const STOP_WORDS = new Set([
|
|
14
|
+
"a",
|
|
15
|
+
"an",
|
|
16
|
+
"the",
|
|
17
|
+
"to",
|
|
18
|
+
"for",
|
|
19
|
+
"of",
|
|
20
|
+
"and",
|
|
21
|
+
"or",
|
|
22
|
+
"plugin",
|
|
23
|
+
"plugins",
|
|
24
|
+
"that",
|
|
25
|
+
"this",
|
|
26
|
+
"my",
|
|
27
|
+
"new",
|
|
28
|
+
"please",
|
|
29
|
+
"create",
|
|
30
|
+
"build",
|
|
31
|
+
"make",
|
|
32
|
+
"i",
|
|
33
|
+
"want",
|
|
34
|
+
"need",
|
|
35
|
+
]);
|
|
36
|
+
const KEBAB_RE = /^[a-z][a-z0-9-]{1,38}[a-z0-9]$/;
|
|
37
|
+
function tokenize(value) {
|
|
38
|
+
return value
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.split(/[^a-z0-9]+/)
|
|
41
|
+
.map((token) => token.trim())
|
|
42
|
+
.filter((token) => token.length > 1 && !STOP_WORDS.has(token));
|
|
43
|
+
}
|
|
44
|
+
function deriveNames(intent) {
|
|
45
|
+
const tokens = tokenize(intent).slice(0, 4);
|
|
46
|
+
const rawSlug = tokens.join("-") || "runtime-plugin";
|
|
47
|
+
const slug = KEBAB_RE.test(rawSlug) ? rawSlug : "runtime-plugin";
|
|
48
|
+
const bareName = slug.startsWith("plugin-") ? slug : `plugin-${slug}`;
|
|
49
|
+
const displayName = tokens.length
|
|
50
|
+
? tokens.map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join(" ")
|
|
51
|
+
: "Runtime Plugin";
|
|
52
|
+
return { packageName: `@elizaos/${bareName}`, displayName };
|
|
53
|
+
}
|
|
54
|
+
function readStringOption(options, key) {
|
|
55
|
+
const direct = options?.[key];
|
|
56
|
+
if (typeof direct === "string" && direct.trim())
|
|
57
|
+
return direct.trim();
|
|
58
|
+
const parameters = options?.parameters;
|
|
59
|
+
if (typeof parameters === "object" &&
|
|
60
|
+
parameters !== null &&
|
|
61
|
+
!Array.isArray(parameters)) {
|
|
62
|
+
const nested = parameters[key];
|
|
63
|
+
if (typeof nested === "string" && nested.trim())
|
|
64
|
+
return nested.trim();
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function isDirectory(file) {
|
|
69
|
+
return fs.stat(file).then((stat) => stat.isDirectory(), () => false);
|
|
70
|
+
}
|
|
71
|
+
async function resolveExistingDirectory(repoRoot, candidates, label) {
|
|
72
|
+
for (const rel of candidates) {
|
|
73
|
+
const candidate = path.join(repoRoot, rel);
|
|
74
|
+
if (await isDirectory(candidate))
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Cannot find ${label}. Tried: ${candidates.map((rel) => path.join(repoRoot, rel)).join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
async function findFreePluginDir(repoRoot, packageName) {
|
|
80
|
+
const pluginsDir = await resolveExistingDirectory(repoRoot, PLUGINS_DIR_CANDIDATES, "plugins directory");
|
|
81
|
+
const baseName = packageName.replace(/^@[^/]+\//, "");
|
|
82
|
+
let candidate = path.join(pluginsDir, baseName);
|
|
83
|
+
let suffix = 2;
|
|
84
|
+
while (await isDirectory(candidate)) {
|
|
85
|
+
candidate = path.join(pluginsDir, `${baseName}-${suffix}`);
|
|
86
|
+
suffix += 1;
|
|
87
|
+
if (suffix > 50) {
|
|
88
|
+
throw new Error(`Could not find a free plugin directory for ${packageName}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return candidate;
|
|
92
|
+
}
|
|
93
|
+
async function copyTemplate(src, dest, replacements) {
|
|
94
|
+
const stack = [{ from: src, to: dest }];
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const current = stack.pop();
|
|
97
|
+
if (!current)
|
|
98
|
+
break;
|
|
99
|
+
const stat = await fs.stat(current.from);
|
|
100
|
+
if (stat.isDirectory()) {
|
|
101
|
+
await fs.mkdir(current.to, { recursive: true });
|
|
102
|
+
for (const entry of await fs.readdir(current.from)) {
|
|
103
|
+
stack.push({
|
|
104
|
+
from: path.join(current.from, entry),
|
|
105
|
+
to: path.join(current.to, entry),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!stat.isFile())
|
|
111
|
+
continue;
|
|
112
|
+
const raw = await fs.readFile(current.from);
|
|
113
|
+
const text = raw.toString("utf8");
|
|
114
|
+
if (Buffer.byteLength(text, "utf8") === raw.length) {
|
|
115
|
+
let rewritten = text;
|
|
116
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
117
|
+
rewritten = rewritten.split(token).join(value);
|
|
118
|
+
}
|
|
119
|
+
await fs.writeFile(current.to, rewritten, "utf8");
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
await fs.writeFile(current.to, raw);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function readStringField(source, key) {
|
|
127
|
+
const value = source[key];
|
|
128
|
+
return typeof value === "string" && value.trim().length > 0
|
|
129
|
+
? value
|
|
130
|
+
: undefined;
|
|
131
|
+
}
|
|
132
|
+
function readTaskAgents(result) {
|
|
133
|
+
const agents = result?.data?.agents;
|
|
134
|
+
if (!Array.isArray(agents))
|
|
135
|
+
return [];
|
|
136
|
+
return agents.flatMap((agent) => {
|
|
137
|
+
if (!agent || typeof agent !== "object" || Array.isArray(agent))
|
|
138
|
+
return [];
|
|
139
|
+
const record = agent;
|
|
140
|
+
const sessionId = readStringField(record, "sessionId");
|
|
141
|
+
const agentType = readStringField(record, "agentType");
|
|
142
|
+
const workdir = readStringField(record, "workdir");
|
|
143
|
+
const label = readStringField(record, "label");
|
|
144
|
+
const status = readStringField(record, "status");
|
|
145
|
+
if (!sessionId || !agentType || !workdir || !label || !status)
|
|
146
|
+
return [];
|
|
147
|
+
return [
|
|
148
|
+
{
|
|
149
|
+
sessionId,
|
|
150
|
+
agentType,
|
|
151
|
+
workdir,
|
|
152
|
+
label,
|
|
153
|
+
status,
|
|
154
|
+
workspaceId: readStringField(record, "workspaceId"),
|
|
155
|
+
branch: readStringField(record, "branch"),
|
|
156
|
+
error: readStringField(record, "error"),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async function dispatchCodingAgent({ runtime, prompt, label, workdir, pluginName, callback, }) {
|
|
162
|
+
const createTask = runtime.actions?.find((action) => action.name === "CREATE_TASK");
|
|
163
|
+
if (!createTask) {
|
|
164
|
+
return { dispatched: false, reason: "CREATE_TASK action not registered" };
|
|
165
|
+
}
|
|
166
|
+
const fakeMessage = {
|
|
167
|
+
entityId: runtime.agentId,
|
|
168
|
+
roomId: runtime.agentId,
|
|
169
|
+
agentId: runtime.agentId,
|
|
170
|
+
content: { text: prompt },
|
|
171
|
+
};
|
|
172
|
+
const handlerOptions = {
|
|
173
|
+
parameters: {
|
|
174
|
+
task: prompt,
|
|
175
|
+
label,
|
|
176
|
+
approvalPreset: "permissive",
|
|
177
|
+
validator: {
|
|
178
|
+
service: "app-verification",
|
|
179
|
+
method: "verifyPlugin",
|
|
180
|
+
params: { workdir, pluginName },
|
|
181
|
+
},
|
|
182
|
+
onVerificationFail: "retry",
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const result = await createTask.handler(runtime, fakeMessage, undefined, handlerOptions, callback);
|
|
186
|
+
if (!result?.success) {
|
|
187
|
+
return {
|
|
188
|
+
dispatched: false,
|
|
189
|
+
reason: result?.text ??
|
|
190
|
+
(typeof result?.error === "string"
|
|
191
|
+
? result.error
|
|
192
|
+
: "CREATE_TASK failed to start"),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const agents = readTaskAgents(result);
|
|
196
|
+
if (agents.length === 0) {
|
|
197
|
+
return {
|
|
198
|
+
dispatched: false,
|
|
199
|
+
reason: "CREATE_TASK did not return a tracked task status",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { dispatched: true, agents };
|
|
203
|
+
}
|
|
204
|
+
function buildCreatePrompt(intent, pluginName, displayName, workdir) {
|
|
205
|
+
return [
|
|
206
|
+
`You are building a new Eliza plugin called "${displayName}".`,
|
|
207
|
+
`The user's intent: ${intent}`,
|
|
208
|
+
`The plugin source directory is ${workdir}. It has already been scaffolded from the min-plugin template.`,
|
|
209
|
+
"Work in that source directory.",
|
|
210
|
+
"Read SCAFFOLD.md before editing.",
|
|
211
|
+
"Before signaling completion, run from the plugin directory: bun run typecheck, then bun run lint, then bun run test.",
|
|
212
|
+
"After all three commands pass, emit exactly one completion line in this canonical schema:",
|
|
213
|
+
`PLUGIN_CREATE_DONE {"pluginName":"${pluginName}","files":["src/index.ts"],"tests":{"passed":1,"failed":0},"lint":"ok","typecheck":"ok"}`,
|
|
214
|
+
"Use files changed or added relative to the source directory.",
|
|
215
|
+
].join("\n");
|
|
216
|
+
}
|
|
217
|
+
function buildEditPrompt(intent, plugin, workdir) {
|
|
218
|
+
return [
|
|
219
|
+
`You are modifying the existing Eliza plugin "${plugin.name}".`,
|
|
220
|
+
`Source lives in ${workdir}.`,
|
|
221
|
+
`User's request: ${intent}`,
|
|
222
|
+
"Implement the requested change without refactoring unrelated code.",
|
|
223
|
+
"Before signaling completion, run from the plugin directory: bun run typecheck, then bun run lint, then bun run test.",
|
|
224
|
+
"After all three commands pass, emit exactly one completion line in this canonical schema:",
|
|
225
|
+
`PLUGIN_CREATE_DONE {"pluginName":"${plugin.name}","files":["src/index.ts"],"tests":{"passed":1,"failed":0},"lint":"ok","typecheck":"ok"}`,
|
|
226
|
+
].join("\n");
|
|
227
|
+
}
|
|
228
|
+
function rankMatches(intent, plugins) {
|
|
229
|
+
const intentTokens = new Set(tokenize(intent));
|
|
230
|
+
if (intentTokens.size === 0)
|
|
231
|
+
return [];
|
|
232
|
+
const ranked = [];
|
|
233
|
+
for (const plugin of plugins) {
|
|
234
|
+
const haystack = tokenize(`${plugin.name} ${plugin.path}`);
|
|
235
|
+
let score = 0;
|
|
236
|
+
for (const token of haystack) {
|
|
237
|
+
if (intentTokens.has(token))
|
|
238
|
+
score += 1;
|
|
239
|
+
}
|
|
240
|
+
if (score > 0)
|
|
241
|
+
ranked.push({ plugin, score });
|
|
242
|
+
}
|
|
243
|
+
return ranked.sort((a, b) => b.score - a.score).slice(0, 5);
|
|
244
|
+
}
|
|
245
|
+
function renderChoiceBlock(choiceId, matches) {
|
|
246
|
+
const lines = [`[CHOICE:plugin-create id=${choiceId}]`, "new = Create new plugin"];
|
|
247
|
+
matches.forEach((match, idx) => {
|
|
248
|
+
lines.push(`edit-${idx + 1} = Edit existing: ${match.plugin.name}`);
|
|
249
|
+
});
|
|
250
|
+
lines.push("cancel = Cancel", "[/CHOICE]");
|
|
251
|
+
return lines.join("\n");
|
|
252
|
+
}
|
|
253
|
+
async function listKnownPlugins(runtime) {
|
|
254
|
+
const service = runtime.getService("plugin_manager");
|
|
255
|
+
if (!service)
|
|
256
|
+
return [];
|
|
257
|
+
const loaded = service.getAllPlugins().map((plugin) => ({
|
|
258
|
+
name: plugin.name,
|
|
259
|
+
path: "",
|
|
260
|
+
version: "loaded",
|
|
261
|
+
upstream: null,
|
|
262
|
+
}));
|
|
263
|
+
const installed = await service.listInstalledPlugins();
|
|
264
|
+
const ejected = await service.listEjectedPlugins();
|
|
265
|
+
const byName = new Map();
|
|
266
|
+
for (const plugin of [...loaded, ...installed, ...ejected]) {
|
|
267
|
+
byName.set(plugin.name, plugin);
|
|
268
|
+
}
|
|
269
|
+
return Array.from(byName.values());
|
|
270
|
+
}
|
|
271
|
+
async function findExistingIntentTask(runtime, roomId) {
|
|
272
|
+
const tasks = await runtime.getTasks({
|
|
273
|
+
agentIds: [runtime.agentId],
|
|
274
|
+
tags: [PLUGIN_CREATE_INTENT_TAG],
|
|
275
|
+
});
|
|
276
|
+
const matching = tasks
|
|
277
|
+
.filter((task) => {
|
|
278
|
+
const meta = task.metadata;
|
|
279
|
+
return meta?.roomId === roomId;
|
|
280
|
+
})
|
|
281
|
+
.sort((a, b) => {
|
|
282
|
+
const aMeta = a.metadata;
|
|
283
|
+
const bMeta = b.metadata;
|
|
284
|
+
const aAt = typeof aMeta?.intentCreatedAt === "string"
|
|
285
|
+
? Date.parse(aMeta.intentCreatedAt)
|
|
286
|
+
: 0;
|
|
287
|
+
const bAt = typeof bMeta?.intentCreatedAt === "string"
|
|
288
|
+
? Date.parse(bMeta.intentCreatedAt)
|
|
289
|
+
: 0;
|
|
290
|
+
return bAt - aAt;
|
|
291
|
+
});
|
|
292
|
+
const top = matching[0];
|
|
293
|
+
if (!top?.id)
|
|
294
|
+
return null;
|
|
295
|
+
const meta = top.metadata;
|
|
296
|
+
if (!meta || typeof meta.intent !== "string")
|
|
297
|
+
return null;
|
|
298
|
+
const choicesRaw = Array.isArray(meta.choices) ? meta.choices : [];
|
|
299
|
+
const choices = choicesRaw
|
|
300
|
+
.filter((choice) => typeof choice === "object" &&
|
|
301
|
+
choice !== null &&
|
|
302
|
+
typeof choice.key === "string" &&
|
|
303
|
+
typeof choice.label === "string")
|
|
304
|
+
.map((choice) => ({
|
|
305
|
+
key: choice.key,
|
|
306
|
+
label: choice.label,
|
|
307
|
+
pluginName: typeof choice.pluginName === "string" ? choice.pluginName : undefined,
|
|
308
|
+
pluginPath: typeof choice.pluginPath === "string" ? choice.pluginPath : undefined,
|
|
309
|
+
}));
|
|
310
|
+
return {
|
|
311
|
+
taskId: top.id,
|
|
312
|
+
metadata: {
|
|
313
|
+
roomId,
|
|
314
|
+
intent: meta.intent,
|
|
315
|
+
choices,
|
|
316
|
+
intentCreatedAt: typeof meta.intentCreatedAt === "string"
|
|
317
|
+
? meta.intentCreatedAt
|
|
318
|
+
: new Date().toISOString(),
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function persistIntentTask(runtime, metadata) {
|
|
323
|
+
await runtime.createTask({
|
|
324
|
+
name: "PLUGIN_CREATE intent",
|
|
325
|
+
description: `Awaiting user choice for: ${metadata.intent}`,
|
|
326
|
+
tags: [PLUGIN_CREATE_INTENT_TAG],
|
|
327
|
+
metadata,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async function deleteIntentTask(runtime, taskId) {
|
|
331
|
+
await runtime.deleteTask(taskId);
|
|
332
|
+
}
|
|
333
|
+
async function createNewPlugin({ runtime, intent, repoRoot, callback, }) {
|
|
334
|
+
const { packageName, displayName } = deriveNames(intent);
|
|
335
|
+
const templateSrc = await resolveExistingDirectory(repoRoot, TEMPLATE_CANDIDATES, "min-plugin template");
|
|
336
|
+
const workdir = await findFreePluginDir(repoRoot, packageName);
|
|
337
|
+
await copyTemplate(templateSrc, workdir, {
|
|
338
|
+
[NAME_PLACEHOLDER]: packageName,
|
|
339
|
+
[DISPLAY_NAME_PLACEHOLDER]: displayName,
|
|
340
|
+
});
|
|
341
|
+
const dispatch = await dispatchCodingAgent({
|
|
342
|
+
runtime,
|
|
343
|
+
prompt: buildCreatePrompt(intent, packageName, displayName, workdir),
|
|
344
|
+
label: `create-plugin:${packageName}`,
|
|
345
|
+
workdir,
|
|
346
|
+
pluginName: packageName,
|
|
347
|
+
callback,
|
|
348
|
+
});
|
|
349
|
+
if (dispatch.dispatched === false) {
|
|
350
|
+
const text = `Scaffolded ${displayName} at ${workdir}, but could not dispatch a coding agent: ${dispatch.reason}.`;
|
|
351
|
+
await callback?.({ text });
|
|
352
|
+
return { success: false, text, values: { mode: "create", workdir } };
|
|
353
|
+
}
|
|
354
|
+
const task = dispatch.agents[0];
|
|
355
|
+
const text = `Started plugin create task for ${displayName} at ${workdir}. Task session ${task.sessionId} is ${task.status}; verification will run when it emits PLUGIN_CREATE_DONE.`;
|
|
356
|
+
await callback?.({ text });
|
|
357
|
+
logger.info(`[plugin-manager] PLUGIN/create new name=${packageName} workdir=${workdir} session=${task.sessionId}`);
|
|
358
|
+
return {
|
|
359
|
+
success: true,
|
|
360
|
+
text,
|
|
361
|
+
values: {
|
|
362
|
+
mode: "create",
|
|
363
|
+
subMode: "new",
|
|
364
|
+
name: packageName,
|
|
365
|
+
displayName,
|
|
366
|
+
workdir,
|
|
367
|
+
taskStatus: task.status,
|
|
368
|
+
taskSessionId: task.sessionId,
|
|
369
|
+
},
|
|
370
|
+
data: { name: packageName, displayName, workdir, task, agents: dispatch.agents },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function editExistingPlugin({ runtime, intent, plugin, callback, }) {
|
|
374
|
+
if (!plugin.path) {
|
|
375
|
+
const text = `Plugin "${plugin.name}" has no local source path. Eject it before editing.`;
|
|
376
|
+
await callback?.({ text });
|
|
377
|
+
return { success: false, text };
|
|
378
|
+
}
|
|
379
|
+
const dispatch = await dispatchCodingAgent({
|
|
380
|
+
runtime,
|
|
381
|
+
prompt: buildEditPrompt(intent, plugin, plugin.path),
|
|
382
|
+
label: `edit-plugin:${plugin.name}`,
|
|
383
|
+
workdir: plugin.path,
|
|
384
|
+
pluginName: plugin.name,
|
|
385
|
+
callback,
|
|
386
|
+
});
|
|
387
|
+
if (dispatch.dispatched === false) {
|
|
388
|
+
const text = `Could not dispatch a coding agent to edit ${plugin.name}: ${dispatch.reason}.`;
|
|
389
|
+
await callback?.({ text });
|
|
390
|
+
return { success: false, text };
|
|
391
|
+
}
|
|
392
|
+
const task = dispatch.agents[0];
|
|
393
|
+
const text = `Started plugin edit task for ${plugin.name} at ${plugin.path}. Task session ${task.sessionId} is ${task.status}; verification will run when it emits PLUGIN_CREATE_DONE.`;
|
|
394
|
+
await callback?.({ text });
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
text,
|
|
398
|
+
values: {
|
|
399
|
+
mode: "create",
|
|
400
|
+
subMode: "edit",
|
|
401
|
+
name: plugin.name,
|
|
402
|
+
workdir: plugin.path,
|
|
403
|
+
taskStatus: task.status,
|
|
404
|
+
taskSessionId: task.sessionId,
|
|
405
|
+
},
|
|
406
|
+
data: { plugin, task, agents: dispatch.agents },
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
export function isPluginCreateChoiceReply(text) {
|
|
410
|
+
return CHOICE_RE.test(text.trim());
|
|
411
|
+
}
|
|
412
|
+
export async function runCreate({ runtime, message, options, callback, intent: explicitIntent, choice: explicitChoice, editTarget, repoRoot, }) {
|
|
413
|
+
const roomId = typeof message.roomId === "string" ? message.roomId : runtime.agentId;
|
|
414
|
+
const userText = (message.content?.text ?? "").trim();
|
|
415
|
+
const optionChoice = readStringOption(options, "choice");
|
|
416
|
+
const optionIntent = readStringOption(options, "intent");
|
|
417
|
+
const optionEditTarget = readStringOption(options, "editTarget");
|
|
418
|
+
const choiceText = explicitChoice ?? optionChoice ?? userText;
|
|
419
|
+
const intent = explicitIntent ?? optionIntent ?? userText;
|
|
420
|
+
const existing = await findExistingIntentTask(runtime, roomId);
|
|
421
|
+
if (existing && isPluginCreateChoiceReply(choiceText)) {
|
|
422
|
+
const normalized = choiceText.toLowerCase().trim();
|
|
423
|
+
await deleteIntentTask(runtime, existing.taskId);
|
|
424
|
+
if (normalized === "cancel") {
|
|
425
|
+
const text = "Canceled. No plugin changes made.";
|
|
426
|
+
await callback?.({ text });
|
|
427
|
+
return { success: true, text, values: { mode: "create", subMode: "cancel" } };
|
|
428
|
+
}
|
|
429
|
+
if (normalized === "new") {
|
|
430
|
+
return createNewPlugin({
|
|
431
|
+
runtime,
|
|
432
|
+
intent: existing.metadata.intent,
|
|
433
|
+
repoRoot,
|
|
434
|
+
callback,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
const idxMatch = normalized.match(/^edit-(\d+)$/);
|
|
438
|
+
const idx = idxMatch ? Number(idxMatch[1]) - 1 : -1;
|
|
439
|
+
const choice = existing.metadata.choices.filter((entry) => entry.key.startsWith("edit-"))[idx];
|
|
440
|
+
const plugins = await listKnownPlugins(runtime);
|
|
441
|
+
const target = plugins.find((plugin) => plugin.name === choice?.pluginName || plugin.path === choice?.pluginPath);
|
|
442
|
+
if (!target) {
|
|
443
|
+
const text = `Plugin edit target "${normalized}" is no longer available.`;
|
|
444
|
+
await callback?.({ text });
|
|
445
|
+
return { success: false, text };
|
|
446
|
+
}
|
|
447
|
+
return editExistingPlugin({
|
|
448
|
+
runtime,
|
|
449
|
+
intent: existing.metadata.intent,
|
|
450
|
+
plugin: target,
|
|
451
|
+
callback,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
const targetName = editTarget ?? optionEditTarget;
|
|
455
|
+
if (targetName) {
|
|
456
|
+
const plugins = await listKnownPlugins(runtime);
|
|
457
|
+
const target = plugins.find((plugin) => plugin.name === targetName || plugin.path === targetName);
|
|
458
|
+
if (!target) {
|
|
459
|
+
const text = `Cannot find a local plugin named "${targetName}".`;
|
|
460
|
+
await callback?.({ text });
|
|
461
|
+
return { success: false, text };
|
|
462
|
+
}
|
|
463
|
+
return editExistingPlugin({ runtime, intent, plugin: target, callback });
|
|
464
|
+
}
|
|
465
|
+
if (!intent) {
|
|
466
|
+
const text = "Tell me what plugin you want to build.";
|
|
467
|
+
await callback?.({ text });
|
|
468
|
+
return { success: false, text };
|
|
469
|
+
}
|
|
470
|
+
const plugins = await listKnownPlugins(runtime);
|
|
471
|
+
const matches = rankMatches(intent, plugins).filter((match) => match.plugin.path);
|
|
472
|
+
if (matches.length === 0) {
|
|
473
|
+
return createNewPlugin({ runtime, intent, repoRoot, callback });
|
|
474
|
+
}
|
|
475
|
+
const choices = [
|
|
476
|
+
{ key: "new", label: "Create new plugin" },
|
|
477
|
+
...matches.map((match, idx) => ({
|
|
478
|
+
key: `edit-${idx + 1}`,
|
|
479
|
+
label: `Edit existing: ${match.plugin.name}`,
|
|
480
|
+
pluginName: match.plugin.name,
|
|
481
|
+
pluginPath: match.plugin.path,
|
|
482
|
+
})),
|
|
483
|
+
{ key: "cancel", label: "Cancel" },
|
|
484
|
+
];
|
|
485
|
+
await persistIntentTask(runtime, {
|
|
486
|
+
roomId,
|
|
487
|
+
intent,
|
|
488
|
+
choices,
|
|
489
|
+
intentCreatedAt: new Date().toISOString(),
|
|
490
|
+
});
|
|
491
|
+
const text = renderChoiceBlock(`plugin-create-${Date.now().toString(36)}`, matches);
|
|
492
|
+
await callback?.({ text });
|
|
493
|
+
return {
|
|
494
|
+
success: true,
|
|
495
|
+
text: "Picking next step...",
|
|
496
|
+
values: { mode: "create", subMode: "choice", matchCount: matches.length },
|
|
497
|
+
data: { choices, intent },
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
export async function hasPendingPluginCreateIntent(runtime, roomId) {
|
|
501
|
+
return (await findExistingIntentTask(runtime, roomId)) !== null;
|
|
502
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/plugin-manager/actions/plugin-handlers/eject
|
|
3
|
+
*
|
|
4
|
+
* `eject` sub-mode of the unified PLUGIN action. Clones a registry plugin
|
|
5
|
+
* into the local ejected directory so the user can edit + sync against
|
|
6
|
+
* upstream.
|
|
7
|
+
*/
|
|
8
|
+
import type { ActionResult, HandlerCallback } from "../../../../types/components.ts";
|
|
9
|
+
import type { IAgentRuntime } from "../../../../types/runtime.ts";
|
|
10
|
+
export interface EjectInput {
|
|
11
|
+
runtime: IAgentRuntime;
|
|
12
|
+
name: string;
|
|
13
|
+
callback?: HandlerCallback;
|
|
14
|
+
}
|
|
15
|
+
export declare function runEject({ runtime, name, callback, }: EjectInput): Promise<ActionResult>;
|
|
16
|
+
//# sourceMappingURL=eject.d.ts.map
|
package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/eject.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eject.d.ts","sourceRoot":"","sources":["../../../../../../../../../typescript/src/features/plugin-manager/actions/plugin-handlers/eject.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAGlE,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,wBAAsB,QAAQ,CAAC,EAC9B,OAAO,EACP,IAAI,EACJ,QAAQ,GACR,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CA8CpC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/plugin-manager/actions/plugin-handlers/eject
|
|
3
|
+
*
|
|
4
|
+
* `eject` sub-mode of the unified PLUGIN action. Clones a registry plugin
|
|
5
|
+
* into the local ejected directory so the user can edit + sync against
|
|
6
|
+
* upstream.
|
|
7
|
+
*/
|
|
8
|
+
export async function runEject({ runtime, name, callback, }) {
|
|
9
|
+
const service = runtime.getService("plugin_manager");
|
|
10
|
+
if (!service) {
|
|
11
|
+
const text = "Plugin manager service not available";
|
|
12
|
+
await callback?.({ text });
|
|
13
|
+
return { success: false, text };
|
|
14
|
+
}
|
|
15
|
+
if (!name) {
|
|
16
|
+
const text = "Specify a plugin name to eject.";
|
|
17
|
+
await callback?.({ text });
|
|
18
|
+
return { success: false, text };
|
|
19
|
+
}
|
|
20
|
+
const result = await service.ejectPlugin(name);
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
const text = `Failed to eject ${name}: ${result.error ?? "unknown error"}`;
|
|
23
|
+
await callback?.({ text });
|
|
24
|
+
return { success: false, text };
|
|
25
|
+
}
|
|
26
|
+
const text = `Ejected ${result.pluginName} to ${result.ejectedPath} ` +
|
|
27
|
+
`(commit ${result.upstreamCommit.slice(0, 8)})` +
|
|
28
|
+
(result.requiresRestart ? "\nRestart required to load the local copy." : "");
|
|
29
|
+
await callback?.({ text });
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
text,
|
|
33
|
+
values: {
|
|
34
|
+
mode: "eject",
|
|
35
|
+
name: result.pluginName,
|
|
36
|
+
ejectedPath: result.ejectedPath,
|
|
37
|
+
upstreamCommit: result.upstreamCommit,
|
|
38
|
+
},
|
|
39
|
+
data: {
|
|
40
|
+
success: result.success,
|
|
41
|
+
pluginName: result.pluginName,
|
|
42
|
+
ejectedPath: result.ejectedPath,
|
|
43
|
+
upstreamCommit: result.upstreamCommit,
|
|
44
|
+
requiresRestart: result.requiresRestart,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/plugin-manager/actions/plugin-handlers/install
|
|
3
|
+
*
|
|
4
|
+
* `install` sub-mode of the unified PLUGIN action. Installs a plugin from
|
|
5
|
+
* the registry by canonical name. The underlying service handles the
|
|
6
|
+
* npm/git source selection internally — `source: "git"` simply forces a
|
|
7
|
+
* clone via the `PLUGIN_MANAGER_LOCAL_CLONE` env override on this call.
|
|
8
|
+
*/
|
|
9
|
+
import type { ActionResult, HandlerCallback } from "../../../../types/components.ts";
|
|
10
|
+
import type { IAgentRuntime } from "../../../../types/runtime.ts";
|
|
11
|
+
export interface InstallInput {
|
|
12
|
+
runtime: IAgentRuntime;
|
|
13
|
+
name: string;
|
|
14
|
+
source?: "npm" | "git";
|
|
15
|
+
callback?: HandlerCallback;
|
|
16
|
+
}
|
|
17
|
+
export declare function runInstall({ runtime, name, source, callback, }: InstallInput): Promise<ActionResult>;
|
|
18
|
+
//# sourceMappingURL=install.d.ts.map
|
package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/install.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../../../../../../../typescript/src/features/plugin-manager/actions/plugin-handlers/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAGlE,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,MAAM,EACN,QAAQ,GACR,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAyDtC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/plugin-manager/actions/plugin-handlers/install
|
|
3
|
+
*
|
|
4
|
+
* `install` sub-mode of the unified PLUGIN action. Installs a plugin from
|
|
5
|
+
* the registry by canonical name. The underlying service handles the
|
|
6
|
+
* npm/git source selection internally — `source: "git"` simply forces a
|
|
7
|
+
* clone via the `PLUGIN_MANAGER_LOCAL_CLONE` env override on this call.
|
|
8
|
+
*/
|
|
9
|
+
import { logger } from "../../../../logger.js";
|
|
10
|
+
export async function runInstall({ runtime, name, source, callback, }) {
|
|
11
|
+
const service = runtime.getService("plugin_manager");
|
|
12
|
+
if (!service) {
|
|
13
|
+
const text = "Plugin manager service not available";
|
|
14
|
+
await callback?.({ text });
|
|
15
|
+
return { success: false, text };
|
|
16
|
+
}
|
|
17
|
+
if (!name) {
|
|
18
|
+
const text = "Specify a plugin name to install (e.g. @elizaos/plugin-twitter).";
|
|
19
|
+
await callback?.({ text });
|
|
20
|
+
return { success: false, text };
|
|
21
|
+
}
|
|
22
|
+
const prevClone = process.env.PLUGIN_MANAGER_LOCAL_CLONE;
|
|
23
|
+
if (source === "git")
|
|
24
|
+
process.env.PLUGIN_MANAGER_LOCAL_CLONE = "true";
|
|
25
|
+
const result = await service.installPlugin(name, (progress) => {
|
|
26
|
+
logger.info(`[plugin-manager] install ${name} ${progress.phase}: ${progress.message}`);
|
|
27
|
+
});
|
|
28
|
+
if (source === "git") {
|
|
29
|
+
if (prevClone === undefined)
|
|
30
|
+
delete process.env.PLUGIN_MANAGER_LOCAL_CLONE;
|
|
31
|
+
else
|
|
32
|
+
process.env.PLUGIN_MANAGER_LOCAL_CLONE = prevClone;
|
|
33
|
+
}
|
|
34
|
+
if (!result.success) {
|
|
35
|
+
const text = `Failed to install ${name}: ${result.error ?? "unknown error"}`;
|
|
36
|
+
await callback?.({ text });
|
|
37
|
+
return { success: false, text };
|
|
38
|
+
}
|
|
39
|
+
const text = `Installed ${result.pluginName}@${result.version} at ${result.installPath}` +
|
|
40
|
+
(result.requiresRestart ? "\nRestart required to activate." : "");
|
|
41
|
+
await callback?.({ text });
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
text,
|
|
45
|
+
values: {
|
|
46
|
+
mode: "install",
|
|
47
|
+
name: result.pluginName,
|
|
48
|
+
version: result.version,
|
|
49
|
+
installPath: result.installPath,
|
|
50
|
+
},
|
|
51
|
+
data: {
|
|
52
|
+
success: result.success,
|
|
53
|
+
pluginName: result.pluginName,
|
|
54
|
+
version: result.version,
|
|
55
|
+
installPath: result.installPath,
|
|
56
|
+
requiresRestart: result.requiresRestart,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
package/packages/typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/plugin-manager/actions/plugin-handlers/list-ejected
|
|
3
|
+
*
|
|
4
|
+
* `list_ejected` sub-mode of the unified PLUGIN action. Lists plugins
|
|
5
|
+
* currently ejected to the local managed directory.
|
|
6
|
+
*/
|
|
7
|
+
import type { ActionResult, HandlerCallback } from "../../../../types/components.ts";
|
|
8
|
+
import type { IAgentRuntime } from "../../../../types/runtime.ts";
|
|
9
|
+
export interface ListEjectedInput {
|
|
10
|
+
runtime: IAgentRuntime;
|
|
11
|
+
callback?: HandlerCallback;
|
|
12
|
+
}
|
|
13
|
+
export declare function runListEjected({ runtime, callback, }: ListEjectedInput): Promise<ActionResult>;
|
|
14
|
+
//# sourceMappingURL=list-ejected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-ejected.d.ts","sourceRoot":"","sources":["../../../../../../../../../typescript/src/features/plugin-manager/actions/plugin-handlers/list-ejected.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAGlE,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAAC,EACpC,OAAO,EACP,QAAQ,GACR,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CA6B1C"}
|