@elizaos/agent 2.0.0-alpha.413 → 2.0.0-alpha.415
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/apps/app-lifeops/src/lifeops/service-mixin-whatsapp.js +1 -90
- package/package.json +4 -4
- package/packages/agent/src/actions/index.d.ts +0 -1
- package/packages/agent/src/actions/index.d.ts.map +1 -1
- package/packages/agent/src/actions/index.js +0 -1
- package/packages/agent/src/api/accounts-routes.d.ts +31 -0
- package/packages/agent/src/api/accounts-routes.d.ts.map +1 -0
- package/packages/agent/src/api/accounts-routes.js +745 -0
- package/packages/agent/src/api/index.d.ts +1 -0
- package/packages/agent/src/api/index.d.ts.map +1 -1
- package/packages/agent/src/api/index.js +1 -0
- package/packages/agent/src/api/model-provider-helpers.js +10 -10
- package/packages/agent/src/api/provider-switch-config.js +2 -2
- package/packages/agent/src/api/server.d.ts.map +1 -1
- package/packages/agent/src/api/server.js +14 -0
- package/packages/agent/src/api/subscription-routes.d.ts.map +1 -1
- package/packages/agent/src/api/subscription-routes.js +48 -1
- package/packages/agent/src/auth/credentials.d.ts.map +1 -1
- package/packages/agent/src/auth/credentials.js +14 -3
- package/packages/agent/src/auth/index.d.ts +2 -0
- package/packages/agent/src/auth/index.d.ts.map +1 -1
- package/packages/agent/src/auth/index.js +2 -0
- package/packages/agent/src/auth/oauth-flow.d.ts +106 -0
- package/packages/agent/src/auth/oauth-flow.d.ts.map +1 -0
- package/packages/agent/src/auth/oauth-flow.js +349 -0
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts +32 -0
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.d.ts.map +1 -1
- package/packages/agent/src/auth/vendor/pi-oauth/anthropic-login.js +63 -28
- package/packages/agent/src/config/schema.js +1 -1
- package/packages/agent/src/config/types.messages.d.ts +1 -1
- package/packages/agent/src/config/types.tools.d.ts +1 -1
- package/packages/agent/src/providers/page-scoped-context.js +1 -1
- package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
- package/packages/agent/src/runtime/eliza-plugin.js +0 -3
- package/packages/agent/src/runtime/eliza.js +3 -3
- package/packages/agent/src/runtime/plugin-collector.js +3 -4
- package/packages/app-core/src/components/conversations/conversation-utils.js +4 -4
- package/packages/app-core/src/components/pages/page-scoped-conversations.js +1 -1
- package/packages/app-core/src/components/settings/ProviderSwitcher.js +1 -1
- package/packages/app-core/src/services/auth-store.js +1 -1
- package/packages/app-core/src/state/useOnboardingState.js +1 -1
- package/packages/shared/src/contracts/lifeops.d.ts +5 -4
- package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
- package/packages/typescript/src/utils/context-catalog.d.ts.map +1 -1
- package/packages/typescript/src/utils/context-catalog.js +2 -3
- package/packages/agent/src/actions/app-control.d.ts +0 -23
- package/packages/agent/src/actions/app-control.d.ts.map +0 -1
- package/packages/agent/src/actions/app-control.js +0 -775
|
@@ -1,775 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LAUNCH_APP / STOP_APP / ATTACH_APP_RUN / LIST_APPS / GET_RUNNING_APPS /
|
|
3
|
-
* FAVORITE_APP / SEND_APP_MESSAGE actions — let the agent control overlay apps.
|
|
4
|
-
*
|
|
5
|
-
* When LAUNCH_APP is triggered:
|
|
6
|
-
* 1. Calls POST /api/apps/launch with the app name
|
|
7
|
-
* 2. Returns a link to the app view
|
|
8
|
-
*
|
|
9
|
-
* When STOP_APP is triggered:
|
|
10
|
-
* 1. Calls POST /api/apps/stop with the app name
|
|
11
|
-
* 2. Returns confirmation
|
|
12
|
-
*
|
|
13
|
-
* @module actions/app-control
|
|
14
|
-
*/
|
|
15
|
-
import { logger } from "@elizaos/core";
|
|
16
|
-
import { getValidationKeywordTerms, normalizeKeywordMatchText, resolveServerOnlyPort, textIncludesKeywordTerm, } from "@elizaos/shared";
|
|
17
|
-
import { hasOwnerAccess } from "../security/access.js";
|
|
18
|
-
const LAUNCH_APP_TERMS = getValidationKeywordTerms("action.appControl.launchVerb", {
|
|
19
|
-
includeAllLocales: true,
|
|
20
|
-
});
|
|
21
|
-
const STOP_APP_TERMS = getValidationKeywordTerms("action.appControl.stopVerb", {
|
|
22
|
-
includeAllLocales: true,
|
|
23
|
-
});
|
|
24
|
-
const GENERIC_APP_TARGET_TERMS = getValidationKeywordTerms("action.appControl.genericTarget", {
|
|
25
|
-
includeAllLocales: true,
|
|
26
|
-
});
|
|
27
|
-
const KNOWN_APP_TERMS = getValidationKeywordTerms("action.appControl.knownApp", {
|
|
28
|
-
includeAllLocales: true,
|
|
29
|
-
});
|
|
30
|
-
const ALL_APP_TARGET_TERMS = [...GENERIC_APP_TARGET_TERMS, ...KNOWN_APP_TERMS];
|
|
31
|
-
function getApiBase() {
|
|
32
|
-
const port = resolveServerOnlyPort(process.env);
|
|
33
|
-
return `http://localhost:${port}`;
|
|
34
|
-
}
|
|
35
|
-
function escapePattern(value) {
|
|
36
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
37
|
-
}
|
|
38
|
-
function containsKeywordTerm(text, terms) {
|
|
39
|
-
return terms.some((term) => textIncludesKeywordTerm(text, term));
|
|
40
|
-
}
|
|
41
|
-
function extractTargetAfterTerms(text, terms) {
|
|
42
|
-
const sortedTerms = [...terms].sort((left, right) => right.length - left.length);
|
|
43
|
-
for (const term of sortedTerms) {
|
|
44
|
-
const pattern = new RegExp(`${escapePattern(term).replace(/\\ /g, "\\s*")}\\s*([\\p{L}\\p{N}_-]+)`, "iu");
|
|
45
|
-
const match = text.match(pattern);
|
|
46
|
-
const candidate = match?.[1]?.trim();
|
|
47
|
-
if (!candidate) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const normalizedCandidate = normalizeKeywordMatchText(candidate);
|
|
51
|
-
if (GENERIC_APP_TARGET_TERMS.some((target) => normalizeKeywordMatchText(target) === normalizedCandidate)) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return candidate.toLowerCase();
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
function extractAppName(message) {
|
|
59
|
-
const text = (message?.content?.text ?? "").trim();
|
|
60
|
-
return (extractTargetAfterTerms(text, LAUNCH_APP_TERMS) ??
|
|
61
|
-
extractTargetAfterTerms(text, STOP_APP_TERMS));
|
|
62
|
-
}
|
|
63
|
-
function isLaunchRequest(message) {
|
|
64
|
-
const text = (message?.content?.text ?? "").trim();
|
|
65
|
-
return (containsKeywordTerm(text, LAUNCH_APP_TERMS) &&
|
|
66
|
-
containsKeywordTerm(text, ALL_APP_TARGET_TERMS));
|
|
67
|
-
}
|
|
68
|
-
function isStopRequest(message) {
|
|
69
|
-
const text = (message?.content?.text ?? "").trim();
|
|
70
|
-
return (containsKeywordTerm(text, STOP_APP_TERMS) &&
|
|
71
|
-
containsKeywordTerm(text, ALL_APP_TARGET_TERMS));
|
|
72
|
-
}
|
|
73
|
-
export const launchAppAction = {
|
|
74
|
-
name: "LAUNCH_APP",
|
|
75
|
-
similes: [
|
|
76
|
-
"OPEN_APP",
|
|
77
|
-
"START_APP",
|
|
78
|
-
"RUN_APP",
|
|
79
|
-
"SHOW_APP",
|
|
80
|
-
"LAUNCH_APPLICATION",
|
|
81
|
-
],
|
|
82
|
-
description: "Launch an overlay app (e.g. Shopify, Vincent, Companion). " +
|
|
83
|
-
"Returns a link to open the app in the dashboard.",
|
|
84
|
-
validate: async (runtime, message) => {
|
|
85
|
-
if (!(await hasOwnerAccess(runtime, message)))
|
|
86
|
-
return false;
|
|
87
|
-
return isLaunchRequest(message);
|
|
88
|
-
},
|
|
89
|
-
handler: async (runtime, message, _state, options) => {
|
|
90
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
91
|
-
return {
|
|
92
|
-
success: false,
|
|
93
|
-
text: "Permission denied: only the owner may launch apps.",
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const params = options?.parameters;
|
|
97
|
-
const appName = params?.name?.trim() || extractAppName(message);
|
|
98
|
-
if (!appName) {
|
|
99
|
-
return {
|
|
100
|
-
success: false,
|
|
101
|
-
text: 'I need the app name to launch. Try: "launch shopify" or "open vincent"',
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
const base = getApiBase();
|
|
106
|
-
const resp = await fetch(`${base}/api/apps/launch`, {
|
|
107
|
-
method: "POST",
|
|
108
|
-
headers: { "Content-Type": "application/json" },
|
|
109
|
-
body: JSON.stringify({ name: appName }),
|
|
110
|
-
signal: AbortSignal.timeout(30_000),
|
|
111
|
-
});
|
|
112
|
-
const data = (await resp.json());
|
|
113
|
-
if (!resp.ok || data.success === false) {
|
|
114
|
-
const errMsg = data.message || `Failed to launch ${appName} (${resp.status})`;
|
|
115
|
-
logger.warn(`[app-control] launch failed: ${errMsg}`);
|
|
116
|
-
return { success: false, text: errMsg };
|
|
117
|
-
}
|
|
118
|
-
const displayName = data.displayName || appName;
|
|
119
|
-
const uiPort = process.env.ELIZA_PORT || "2138";
|
|
120
|
-
const appLink = `http://localhost:${uiPort}/#/apps/${appName}`;
|
|
121
|
-
logger.info(`[app-control] launched ${displayName}`);
|
|
122
|
-
return {
|
|
123
|
-
success: true,
|
|
124
|
-
text: `${displayName} is now running. Open it here: ${appLink}`,
|
|
125
|
-
values: { appName, displayName, appLink },
|
|
126
|
-
data: { run: data.run },
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
-
logger.error(`[app-control] launch error: ${msg}`);
|
|
132
|
-
return { success: false, text: `Failed to launch ${appName}: ${msg}` };
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
parameters: [
|
|
136
|
-
{
|
|
137
|
-
name: "name",
|
|
138
|
-
description: "The app name or slug to launch (e.g. 'shopify', 'vincent', 'companion').",
|
|
139
|
-
required: true,
|
|
140
|
-
schema: { type: "string" },
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
examples: [
|
|
144
|
-
[
|
|
145
|
-
{
|
|
146
|
-
name: "{{name1}}",
|
|
147
|
-
content: {
|
|
148
|
-
text: "Fire up the Shopify app.",
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: "{{agentName}}",
|
|
153
|
-
content: {
|
|
154
|
-
text: "Shopify is now running. Open it here: http://localhost:2138/#/apps/shopify",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
[
|
|
159
|
-
{
|
|
160
|
-
name: "{{name1}}",
|
|
161
|
-
content: {
|
|
162
|
-
text: "Open the companion overlay on my screen.",
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: "{{agentName}}",
|
|
167
|
-
content: {
|
|
168
|
-
text: "Companion is now running. Open it here: http://localhost:2138/#/apps/companion",
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
export const stopAppAction = {
|
|
175
|
-
name: "STOP_APP",
|
|
176
|
-
similes: [
|
|
177
|
-
"CLOSE_APP",
|
|
178
|
-
"SHUTDOWN_APP",
|
|
179
|
-
"KILL_APP",
|
|
180
|
-
"QUIT_APP",
|
|
181
|
-
"EXIT_APP",
|
|
182
|
-
"STOP_APPLICATION",
|
|
183
|
-
],
|
|
184
|
-
description: "Stop a running overlay app by name. Uninstalls the plugin and tears " +
|
|
185
|
-
"down the viewer session.",
|
|
186
|
-
validate: async (runtime, message) => {
|
|
187
|
-
if (!(await hasOwnerAccess(runtime, message)))
|
|
188
|
-
return false;
|
|
189
|
-
return isStopRequest(message);
|
|
190
|
-
},
|
|
191
|
-
handler: async (runtime, message, _state, options) => {
|
|
192
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
193
|
-
return {
|
|
194
|
-
success: false,
|
|
195
|
-
text: "Permission denied: only the owner may stop apps.",
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
const params = options?.parameters;
|
|
199
|
-
const appName = params?.name?.trim() || extractAppName(message);
|
|
200
|
-
if (!appName) {
|
|
201
|
-
return {
|
|
202
|
-
success: false,
|
|
203
|
-
text: 'I need the app name to stop. Try: "stop shopify" or "close vincent"',
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
const base = getApiBase();
|
|
208
|
-
const resp = await fetch(`${base}/api/apps/stop`, {
|
|
209
|
-
method: "POST",
|
|
210
|
-
headers: { "Content-Type": "application/json" },
|
|
211
|
-
body: JSON.stringify({ name: appName }),
|
|
212
|
-
signal: AbortSignal.timeout(15_000),
|
|
213
|
-
});
|
|
214
|
-
const data = (await resp.json());
|
|
215
|
-
if (!resp.ok || data.success === false) {
|
|
216
|
-
const errMsg = data.message || `Failed to stop ${appName} (${resp.status})`;
|
|
217
|
-
logger.warn(`[app-control] stop failed: ${errMsg}`);
|
|
218
|
-
return { success: false, text: errMsg };
|
|
219
|
-
}
|
|
220
|
-
const msg = data.message || `${appName} has been stopped.`;
|
|
221
|
-
logger.info(`[app-control] stopped ${appName}`);
|
|
222
|
-
return {
|
|
223
|
-
success: true,
|
|
224
|
-
text: msg,
|
|
225
|
-
values: { appName },
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
230
|
-
logger.error(`[app-control] stop error: ${msg}`);
|
|
231
|
-
return { success: false, text: `Failed to stop ${appName}: ${msg}` };
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
parameters: [
|
|
235
|
-
{
|
|
236
|
-
name: "name",
|
|
237
|
-
description: "The app name or slug to stop (e.g. 'shopify', 'vincent', 'companion').",
|
|
238
|
-
required: true,
|
|
239
|
-
schema: { type: "string" },
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
examples: [
|
|
243
|
-
[
|
|
244
|
-
{
|
|
245
|
-
name: "{{name1}}",
|
|
246
|
-
content: {
|
|
247
|
-
text: "Shut down Shopify, I'm done with it for now.",
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: "{{agentName}}",
|
|
252
|
-
content: {
|
|
253
|
-
text: "shopify has been stopped.",
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
],
|
|
257
|
-
[
|
|
258
|
-
{
|
|
259
|
-
name: "{{name1}}",
|
|
260
|
-
content: {
|
|
261
|
-
text: "Close the companion overlay.",
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "{{agentName}}",
|
|
266
|
-
content: {
|
|
267
|
-
text: "companion has been stopped.",
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
],
|
|
272
|
-
};
|
|
273
|
-
export const attachAppRunAction = {
|
|
274
|
-
name: "ATTACH_APP_RUN",
|
|
275
|
-
similes: [
|
|
276
|
-
"VIEW_APP_RUN",
|
|
277
|
-
"OPEN_APP_RUN",
|
|
278
|
-
"RESUME_APP_RUN",
|
|
279
|
-
"ATTACH_RUN",
|
|
280
|
-
"WATCH_APP_RUN",
|
|
281
|
-
],
|
|
282
|
-
description: "Attach to a live running app run so its viewer is shown in the dashboard. " +
|
|
283
|
-
"Use this when the user wants to see or resume an app that is already running.",
|
|
284
|
-
validate: async (runtime, message) => {
|
|
285
|
-
return hasOwnerAccess(runtime, message);
|
|
286
|
-
},
|
|
287
|
-
handler: async (runtime, message, _state, options) => {
|
|
288
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
289
|
-
return {
|
|
290
|
-
success: false,
|
|
291
|
-
text: "Permission denied: only the owner may attach app runs.",
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
const params = options?.parameters;
|
|
295
|
-
const runId = params?.runId?.trim();
|
|
296
|
-
if (!runId) {
|
|
297
|
-
return {
|
|
298
|
-
success: false,
|
|
299
|
-
text: "I need a runId to attach to.",
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
const base = getApiBase();
|
|
304
|
-
const resp = await fetch(`${base}/api/apps/runs/${encodeURIComponent(runId)}/attach`, {
|
|
305
|
-
method: "POST",
|
|
306
|
-
headers: { "Content-Type": "application/json" },
|
|
307
|
-
signal: AbortSignal.timeout(15_000),
|
|
308
|
-
});
|
|
309
|
-
const data = (await resp.json().catch(() => ({})));
|
|
310
|
-
if (!resp.ok || data.success === false) {
|
|
311
|
-
const errMsg = data.message || `Failed to attach run ${runId} (${resp.status})`;
|
|
312
|
-
logger.warn(`[app-control] attach failed: ${errMsg}`);
|
|
313
|
-
return { success: false, text: errMsg };
|
|
314
|
-
}
|
|
315
|
-
const run = data.run;
|
|
316
|
-
const displayName = run?.displayName ?? runId;
|
|
317
|
-
const uiPort = process.env.ELIZA_PORT || "2138";
|
|
318
|
-
const appLink = run
|
|
319
|
-
? `http://localhost:${uiPort}/#/apps/${run.appName}`
|
|
320
|
-
: `http://localhost:${uiPort}/#/apps`;
|
|
321
|
-
logger.info(`[app-control] attached run ${runId}`);
|
|
322
|
-
return {
|
|
323
|
-
success: true,
|
|
324
|
-
text: `Attached to ${displayName}. Open it here: ${appLink}`,
|
|
325
|
-
values: { runId, appLink, displayName },
|
|
326
|
-
data: { run },
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
catch (err) {
|
|
330
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
331
|
-
logger.error(`[app-control] attach error: ${msg}`);
|
|
332
|
-
return { success: false, text: `Failed to attach run ${runId}: ${msg}` };
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
parameters: [
|
|
336
|
-
{
|
|
337
|
-
name: "runId",
|
|
338
|
-
description: "The run ID of the app run to attach to.",
|
|
339
|
-
required: true,
|
|
340
|
-
schema: { type: "string" },
|
|
341
|
-
},
|
|
342
|
-
],
|
|
343
|
-
examples: [
|
|
344
|
-
[
|
|
345
|
-
{
|
|
346
|
-
name: "{{name1}}",
|
|
347
|
-
content: {
|
|
348
|
-
text: "Show me the running shopify app again.",
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
name: "{{agentName}}",
|
|
353
|
-
content: {
|
|
354
|
-
text: "Attached to Shopify. Open it here: http://localhost:2138/#/apps/shopify",
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
],
|
|
359
|
-
};
|
|
360
|
-
function isAppListFilter(value) {
|
|
361
|
-
return value === "catalog" || value === "installed" || value === "running";
|
|
362
|
-
}
|
|
363
|
-
export const listAppsAction = {
|
|
364
|
-
name: "LIST_APPS",
|
|
365
|
-
similes: [
|
|
366
|
-
"SHOW_APPS",
|
|
367
|
-
"ENUMERATE_APPS",
|
|
368
|
-
"WHICH_APPS",
|
|
369
|
-
"AVAILABLE_APPS",
|
|
370
|
-
"GET_APPS",
|
|
371
|
-
],
|
|
372
|
-
description: "List apps that the user can launch. Filter by 'catalog' for the full " +
|
|
373
|
-
"registry, 'installed' for locally installed apps, or 'running' for live " +
|
|
374
|
-
"runs. Defaults to the catalog.",
|
|
375
|
-
validate: async (runtime, message) => {
|
|
376
|
-
return hasOwnerAccess(runtime, message);
|
|
377
|
-
},
|
|
378
|
-
handler: async (runtime, message, _state, options) => {
|
|
379
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
380
|
-
return {
|
|
381
|
-
success: false,
|
|
382
|
-
text: "Permission denied: only the owner may list apps.",
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
const params = options?.parameters;
|
|
386
|
-
const filter = isAppListFilter(params?.filter)
|
|
387
|
-
? params.filter
|
|
388
|
-
: "catalog";
|
|
389
|
-
const base = getApiBase();
|
|
390
|
-
try {
|
|
391
|
-
if (filter === "running") {
|
|
392
|
-
const resp = await fetch(`${base}/api/apps/runs`, {
|
|
393
|
-
signal: AbortSignal.timeout(15_000),
|
|
394
|
-
});
|
|
395
|
-
const runs = (await resp
|
|
396
|
-
.json()
|
|
397
|
-
.catch(() => []));
|
|
398
|
-
if (!resp.ok) {
|
|
399
|
-
return {
|
|
400
|
-
success: false,
|
|
401
|
-
text: `Failed to list running apps (${resp.status}).`,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
if (!Array.isArray(runs) || runs.length === 0) {
|
|
405
|
-
return {
|
|
406
|
-
success: true,
|
|
407
|
-
text: "No apps are currently running.",
|
|
408
|
-
data: { filter, count: 0, runs: [] },
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const lines = runs.map((run) => `- ${run.displayName} [${run.appName}] (run ${run.runId.slice(0, 8)}, ${run.status ?? "unknown"})`);
|
|
412
|
-
return {
|
|
413
|
-
success: true,
|
|
414
|
-
text: [`Running apps (${runs.length}):`, ...lines].join("\n"),
|
|
415
|
-
data: { filter, count: runs.length, runs },
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
const path = filter === "installed" ? "/api/apps/installed" : "/api/apps";
|
|
419
|
-
const resp = await fetch(`${base}${path}`, {
|
|
420
|
-
signal: AbortSignal.timeout(15_000),
|
|
421
|
-
});
|
|
422
|
-
const apps = (await resp.json().catch(() => []));
|
|
423
|
-
if (!resp.ok) {
|
|
424
|
-
return {
|
|
425
|
-
success: false,
|
|
426
|
-
text: `Failed to list apps (${resp.status}).`,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
if (!Array.isArray(apps) || apps.length === 0) {
|
|
430
|
-
return {
|
|
431
|
-
success: true,
|
|
432
|
-
text: filter === "installed"
|
|
433
|
-
? "No apps are installed."
|
|
434
|
-
: "No apps available in the catalog.",
|
|
435
|
-
data: { filter, count: 0, apps: [] },
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
const lines = apps.map((app) => {
|
|
439
|
-
const display = app.displayName ?? app.name;
|
|
440
|
-
return `- ${display} [${app.name}]`;
|
|
441
|
-
});
|
|
442
|
-
return {
|
|
443
|
-
success: true,
|
|
444
|
-
text: [
|
|
445
|
-
`${filter === "installed" ? "Installed" : "Catalog"} apps (${apps.length}):`,
|
|
446
|
-
...lines,
|
|
447
|
-
].join("\n"),
|
|
448
|
-
data: { filter, count: apps.length, apps },
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
453
|
-
logger.error(`[app-control] list apps error: ${msg}`);
|
|
454
|
-
return { success: false, text: `Failed to list apps: ${msg}` };
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
parameters: [
|
|
458
|
-
{
|
|
459
|
-
name: "filter",
|
|
460
|
-
description: "Which apps to list: 'catalog' (default, all known apps), 'installed', or 'running'.",
|
|
461
|
-
required: false,
|
|
462
|
-
schema: {
|
|
463
|
-
type: "string",
|
|
464
|
-
enum: ["catalog", "installed", "running"],
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
examples: [
|
|
469
|
-
[
|
|
470
|
-
{
|
|
471
|
-
name: "{{name1}}",
|
|
472
|
-
content: { text: "Which apps can I launch?" },
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
name: "{{agentName}}",
|
|
476
|
-
content: {
|
|
477
|
-
text: "Catalog apps (3):\n- Shopify [shopify]\n- Vincent [vincent]\n- Companion [companion]",
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
],
|
|
481
|
-
[
|
|
482
|
-
{
|
|
483
|
-
name: "{{name1}}",
|
|
484
|
-
content: { text: "What apps are installed locally?" },
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
name: "{{agentName}}",
|
|
488
|
-
content: {
|
|
489
|
-
text: "Installed apps (1):\n- Companion [companion]",
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
],
|
|
493
|
-
],
|
|
494
|
-
};
|
|
495
|
-
// ---------------------------------------------------------------------------
|
|
496
|
-
// GET_RUNNING_APPS — list the currently active app runs with health.
|
|
497
|
-
// ---------------------------------------------------------------------------
|
|
498
|
-
export const getRunningAppsAction = {
|
|
499
|
-
name: "GET_RUNNING_APPS",
|
|
500
|
-
similes: [
|
|
501
|
-
"LIST_RUNNING_APPS",
|
|
502
|
-
"WHICH_APPS_ARE_RUNNING",
|
|
503
|
-
"ACTIVE_APP_RUNS",
|
|
504
|
-
"SHOW_LIVE_APPS",
|
|
505
|
-
"APP_RUNS",
|
|
506
|
-
],
|
|
507
|
-
description: "Return the set of app runs currently active on this agent, with their " +
|
|
508
|
-
"run IDs, app names, status, and viewer attachment state.",
|
|
509
|
-
validate: async (runtime, message) => {
|
|
510
|
-
return hasOwnerAccess(runtime, message);
|
|
511
|
-
},
|
|
512
|
-
handler: async (runtime, message) => {
|
|
513
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
514
|
-
return {
|
|
515
|
-
success: false,
|
|
516
|
-
text: "Permission denied: only the owner may view running apps.",
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
try {
|
|
520
|
-
const base = getApiBase();
|
|
521
|
-
const resp = await fetch(`${base}/api/apps/runs`, {
|
|
522
|
-
signal: AbortSignal.timeout(15_000),
|
|
523
|
-
});
|
|
524
|
-
const runs = (await resp.json().catch(() => []));
|
|
525
|
-
if (!resp.ok) {
|
|
526
|
-
return {
|
|
527
|
-
success: false,
|
|
528
|
-
text: `Failed to list app runs (${resp.status}).`,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
if (!Array.isArray(runs) || runs.length === 0) {
|
|
532
|
-
return {
|
|
533
|
-
success: true,
|
|
534
|
-
text: "No apps are currently running.",
|
|
535
|
-
data: { count: 0, runs: [] },
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
const lines = runs.map((run) => {
|
|
539
|
-
const status = run.status ?? "unknown";
|
|
540
|
-
const attachment = run.viewerAttachment ?? "detached";
|
|
541
|
-
return `- ${run.displayName} [${run.appName}] runId=${run.runId.slice(0, 8)} status=${status} viewer=${attachment}`;
|
|
542
|
-
});
|
|
543
|
-
return {
|
|
544
|
-
success: true,
|
|
545
|
-
text: [`Active app runs (${runs.length}):`, ...lines].join("\n"),
|
|
546
|
-
data: { count: runs.length, runs },
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
catch (err) {
|
|
550
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
551
|
-
logger.error(`[app-control] get running apps error: ${msg}`);
|
|
552
|
-
return {
|
|
553
|
-
success: false,
|
|
554
|
-
text: `Failed to list running apps: ${msg}`,
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
},
|
|
558
|
-
parameters: [],
|
|
559
|
-
examples: [
|
|
560
|
-
[
|
|
561
|
-
{
|
|
562
|
-
name: "{{name1}}",
|
|
563
|
-
content: { text: "What apps are running right now?" },
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: "{{agentName}}",
|
|
567
|
-
content: {
|
|
568
|
-
text: "Active app runs (1):\n- Shopify [shopify] runId=abc12345 status=running viewer=attached",
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
],
|
|
572
|
-
],
|
|
573
|
-
};
|
|
574
|
-
// ---------------------------------------------------------------------------
|
|
575
|
-
// FAVORITE_APP — pin or unpin an app from the favorites list.
|
|
576
|
-
//
|
|
577
|
-
// Persists via PUT /api/apps/favorites which writes the list to the
|
|
578
|
-
// runtime config (config.ui.favoriteApps). The dashboard reads the same
|
|
579
|
-
// list on mount so changes show up without a refresh.
|
|
580
|
-
// ---------------------------------------------------------------------------
|
|
581
|
-
export const favoriteAppAction = {
|
|
582
|
-
name: "FAVORITE_APP",
|
|
583
|
-
similes: ["PIN_APP", "STAR_APP", "BOOKMARK_APP", "UNFAVORITE_APP"],
|
|
584
|
-
description: "Pin or unpin an app in the dashboard favorites list. Persists via " +
|
|
585
|
-
"PUT /api/apps/favorites on the local runtime.",
|
|
586
|
-
validate: async (runtime, message) => {
|
|
587
|
-
return hasOwnerAccess(runtime, message);
|
|
588
|
-
},
|
|
589
|
-
handler: async (runtime, message, _state, options) => {
|
|
590
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
591
|
-
return {
|
|
592
|
-
success: false,
|
|
593
|
-
text: "Permission denied: only the owner may change favorites.",
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
const params = options?.parameters;
|
|
597
|
-
const appName = params?.appName?.trim();
|
|
598
|
-
const isFavorite = params?.isFavorite;
|
|
599
|
-
if (!appName) {
|
|
600
|
-
return {
|
|
601
|
-
success: false,
|
|
602
|
-
text: "I need an app name to favorite or unfavorite.",
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
if (typeof isFavorite !== "boolean") {
|
|
606
|
-
return {
|
|
607
|
-
success: false,
|
|
608
|
-
text: "I need to know whether to favorite (true) or unfavorite (false).",
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
|
-
const base = getApiBase();
|
|
613
|
-
const resp = await fetch(`${base}/api/apps/favorites`, {
|
|
614
|
-
method: "PUT",
|
|
615
|
-
headers: { "Content-Type": "application/json" },
|
|
616
|
-
body: JSON.stringify({ appName, isFavorite }),
|
|
617
|
-
signal: AbortSignal.timeout(10_000),
|
|
618
|
-
});
|
|
619
|
-
const data = (await resp.json().catch(() => ({})));
|
|
620
|
-
if (!resp.ok) {
|
|
621
|
-
const errMsg = data.error || `HTTP ${resp.status}`;
|
|
622
|
-
logger.warn(`[app-control] favorite update failed: ${errMsg}`);
|
|
623
|
-
return {
|
|
624
|
-
success: false,
|
|
625
|
-
text: `Failed to ${isFavorite ? "favorite" : "unfavorite"} ${appName}: ${errMsg}`,
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
const favorites = Array.isArray(data.favoriteApps)
|
|
629
|
-
? data.favoriteApps
|
|
630
|
-
: [];
|
|
631
|
-
const verb = isFavorite ? "Favorited" : "Unfavorited";
|
|
632
|
-
logger.info(`[app-control] ${verb.toLowerCase()} ${appName} (${favorites.length} total)`);
|
|
633
|
-
return {
|
|
634
|
-
success: true,
|
|
635
|
-
text: `${verb} ${appName}.`,
|
|
636
|
-
values: { appName, isFavorite, count: favorites.length },
|
|
637
|
-
data: { appName, isFavorite, favoriteApps: favorites },
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
catch (err) {
|
|
641
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
642
|
-
logger.error(`[app-control] favorite error: ${msg}`);
|
|
643
|
-
return {
|
|
644
|
-
success: false,
|
|
645
|
-
text: `Failed to update favorite for ${appName}: ${msg}`,
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
},
|
|
649
|
-
parameters: [
|
|
650
|
-
{
|
|
651
|
-
name: "appName",
|
|
652
|
-
description: "The app name or slug to (un)favorite.",
|
|
653
|
-
required: true,
|
|
654
|
-
schema: { type: "string" },
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
name: "isFavorite",
|
|
658
|
-
description: "true to add to favorites, false to remove.",
|
|
659
|
-
required: true,
|
|
660
|
-
schema: { type: "boolean" },
|
|
661
|
-
},
|
|
662
|
-
],
|
|
663
|
-
examples: [
|
|
664
|
-
[
|
|
665
|
-
{
|
|
666
|
-
name: "{{name1}}",
|
|
667
|
-
content: { text: "Pin shopify to my favorites." },
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
name: "{{agentName}}",
|
|
671
|
-
content: {
|
|
672
|
-
text: "Favorited shopify.",
|
|
673
|
-
action: "FAVORITE_APP",
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
],
|
|
677
|
-
],
|
|
678
|
-
};
|
|
679
|
-
// ---------------------------------------------------------------------------
|
|
680
|
-
// SEND_APP_MESSAGE — steer a running app run by sending a chat message.
|
|
681
|
-
// ---------------------------------------------------------------------------
|
|
682
|
-
export const sendAppMessageAction = {
|
|
683
|
-
name: "SEND_APP_MESSAGE",
|
|
684
|
-
similes: [
|
|
685
|
-
"STEER_APP_RUN",
|
|
686
|
-
"MESSAGE_APP_RUN",
|
|
687
|
-
"TELL_APP",
|
|
688
|
-
"SEND_TO_APP",
|
|
689
|
-
"POST_APP_MESSAGE",
|
|
690
|
-
],
|
|
691
|
-
description: "Send a chat message to a running app run. Apps that accept user input " +
|
|
692
|
-
"(e.g. coding agents, character apps) will receive the message via " +
|
|
693
|
-
"POST /api/apps/runs/{runId}/message.",
|
|
694
|
-
validate: async (runtime, message) => {
|
|
695
|
-
return hasOwnerAccess(runtime, message);
|
|
696
|
-
},
|
|
697
|
-
handler: async (runtime, message, _state, options) => {
|
|
698
|
-
if (!(await hasOwnerAccess(runtime, message))) {
|
|
699
|
-
return {
|
|
700
|
-
success: false,
|
|
701
|
-
text: "Permission denied: only the owner may send app messages.",
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
const params = options?.parameters;
|
|
705
|
-
const runId = params?.runId?.trim();
|
|
706
|
-
const content = params?.message?.trim();
|
|
707
|
-
if (!runId) {
|
|
708
|
-
return { success: false, text: "I need a runId to send a message to." };
|
|
709
|
-
}
|
|
710
|
-
if (!content) {
|
|
711
|
-
return { success: false, text: "I need a message to send." };
|
|
712
|
-
}
|
|
713
|
-
try {
|
|
714
|
-
const base = getApiBase();
|
|
715
|
-
const resp = await fetch(`${base}/api/apps/runs/${encodeURIComponent(runId)}/message`, {
|
|
716
|
-
method: "POST",
|
|
717
|
-
headers: { "Content-Type": "application/json" },
|
|
718
|
-
body: JSON.stringify({ content }),
|
|
719
|
-
signal: AbortSignal.timeout(30_000),
|
|
720
|
-
});
|
|
721
|
-
const data = (await resp.json().catch(() => ({})));
|
|
722
|
-
if (!resp.ok || data.success === false) {
|
|
723
|
-
const errMsg = data.message || `Failed to send message (${resp.status}).`;
|
|
724
|
-
logger.warn(`[app-control] send message failed: ${errMsg}`);
|
|
725
|
-
return { success: false, text: errMsg };
|
|
726
|
-
}
|
|
727
|
-
const replyMsg = data.message || `Message delivered to run ${runId}.`;
|
|
728
|
-
logger.info(`[app-control] sent message to run ${runId}`);
|
|
729
|
-
return {
|
|
730
|
-
success: true,
|
|
731
|
-
text: replyMsg,
|
|
732
|
-
values: { runId },
|
|
733
|
-
data: { runId, response: data },
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
catch (err) {
|
|
737
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
738
|
-
logger.error(`[app-control] send message error: ${msg}`);
|
|
739
|
-
return {
|
|
740
|
-
success: false,
|
|
741
|
-
text: `Failed to send message to run ${runId}: ${msg}`,
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
parameters: [
|
|
746
|
-
{
|
|
747
|
-
name: "runId",
|
|
748
|
-
description: "The run ID of the target app run.",
|
|
749
|
-
required: true,
|
|
750
|
-
schema: { type: "string" },
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
name: "message",
|
|
754
|
-
description: "The message text to deliver to the app run.",
|
|
755
|
-
required: true,
|
|
756
|
-
schema: { type: "string" },
|
|
757
|
-
},
|
|
758
|
-
],
|
|
759
|
-
examples: [
|
|
760
|
-
[
|
|
761
|
-
{
|
|
762
|
-
name: "{{name1}}",
|
|
763
|
-
content: {
|
|
764
|
-
text: "Tell the steward run to start a new task: refactor the api module.",
|
|
765
|
-
},
|
|
766
|
-
},
|
|
767
|
-
{
|
|
768
|
-
name: "{{agentName}}",
|
|
769
|
-
content: {
|
|
770
|
-
text: "Message delivered to run abc12345.",
|
|
771
|
-
},
|
|
772
|
-
},
|
|
773
|
-
],
|
|
774
|
-
],
|
|
775
|
-
};
|