@elizaos/autonomous 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +270 -0
  3. package/src/actions/emote.ts +101 -0
  4. package/src/actions/restart.ts +101 -0
  5. package/src/actions/send-message.ts +168 -0
  6. package/src/actions/stream-control.ts +439 -0
  7. package/src/actions/switch-stream-source.ts +126 -0
  8. package/src/actions/terminal.ts +186 -0
  9. package/src/api/agent-admin-routes.ts +178 -0
  10. package/src/api/agent-lifecycle-routes.ts +129 -0
  11. package/src/api/agent-model.ts +143 -0
  12. package/src/api/agent-transfer-routes.ts +211 -0
  13. package/src/api/apps-routes.ts +210 -0
  14. package/src/api/auth-routes.ts +90 -0
  15. package/src/api/bsc-trade.ts +736 -0
  16. package/src/api/bug-report-routes.ts +161 -0
  17. package/src/api/character-routes.ts +421 -0
  18. package/src/api/cloud-billing-routes.ts +598 -0
  19. package/src/api/cloud-compat-routes.ts +192 -0
  20. package/src/api/cloud-routes.ts +529 -0
  21. package/src/api/cloud-status-routes.ts +234 -0
  22. package/src/api/compat-utils.ts +154 -0
  23. package/src/api/connector-health.ts +135 -0
  24. package/src/api/coordinator-wiring.ts +179 -0
  25. package/src/api/credit-detection.ts +47 -0
  26. package/src/api/database.ts +1357 -0
  27. package/src/api/diagnostics-routes.ts +389 -0
  28. package/src/api/drop-service.ts +205 -0
  29. package/src/api/early-logs.ts +111 -0
  30. package/src/api/http-helpers.ts +252 -0
  31. package/src/api/index.ts +85 -0
  32. package/src/api/knowledge-routes.ts +1189 -0
  33. package/src/api/knowledge-service-loader.ts +92 -0
  34. package/src/api/memory-bounds.ts +121 -0
  35. package/src/api/memory-routes.ts +349 -0
  36. package/src/api/merkle-tree.ts +239 -0
  37. package/src/api/models-routes.ts +72 -0
  38. package/src/api/nfa-routes.ts +169 -0
  39. package/src/api/nft-verify.ts +188 -0
  40. package/src/api/og-tracker.ts +72 -0
  41. package/src/api/parse-action-block.ts +145 -0
  42. package/src/api/permissions-routes.ts +222 -0
  43. package/src/api/plugin-validation.ts +355 -0
  44. package/src/api/provider-switch-config.ts +455 -0
  45. package/src/api/registry-routes.ts +165 -0
  46. package/src/api/registry-service.ts +292 -0
  47. package/src/api/route-helpers.ts +21 -0
  48. package/src/api/sandbox-routes.ts +1480 -0
  49. package/src/api/server.ts +17674 -0
  50. package/src/api/signal-routes.ts +265 -0
  51. package/src/api/stream-persistence.ts +297 -0
  52. package/src/api/stream-route-state.ts +48 -0
  53. package/src/api/stream-routes.ts +1046 -0
  54. package/src/api/stream-voice-routes.ts +208 -0
  55. package/src/api/streaming-text.ts +129 -0
  56. package/src/api/streaming-types.ts +23 -0
  57. package/src/api/subscription-routes.ts +283 -0
  58. package/src/api/terminal-run-limits.ts +31 -0
  59. package/src/api/training-backend-check.ts +40 -0
  60. package/src/api/training-routes.ts +314 -0
  61. package/src/api/training-service-like.ts +46 -0
  62. package/src/api/trajectory-routes.ts +714 -0
  63. package/src/api/trigger-routes.ts +438 -0
  64. package/src/api/twitter-verify.ts +226 -0
  65. package/src/api/tx-service.ts +193 -0
  66. package/src/api/wallet-dex-prices.ts +206 -0
  67. package/src/api/wallet-evm-balance.ts +989 -0
  68. package/src/api/wallet-routes.ts +505 -0
  69. package/src/api/wallet-rpc.ts +523 -0
  70. package/src/api/wallet-trading-profile.ts +694 -0
  71. package/src/api/wallet.ts +745 -0
  72. package/src/api/whatsapp-routes.ts +282 -0
  73. package/src/api/zip-utils.ts +130 -0
  74. package/src/auth/anthropic.ts +63 -0
  75. package/src/auth/apply-stealth.ts +38 -0
  76. package/src/auth/claude-code-stealth.ts +141 -0
  77. package/src/auth/credentials.ts +226 -0
  78. package/src/auth/index.ts +18 -0
  79. package/src/auth/openai-codex.ts +94 -0
  80. package/src/auth/types.ts +24 -0
  81. package/src/awareness/registry.ts +220 -0
  82. package/src/bin.ts +10 -0
  83. package/src/cli/index.ts +36 -0
  84. package/src/cli/parse-duration.ts +43 -0
  85. package/src/cloud/auth.test.ts +370 -0
  86. package/src/cloud/auth.ts +176 -0
  87. package/src/cloud/backup.test.ts +150 -0
  88. package/src/cloud/backup.ts +50 -0
  89. package/src/cloud/base-url.ts +45 -0
  90. package/src/cloud/bridge-client.test.ts +481 -0
  91. package/src/cloud/bridge-client.ts +307 -0
  92. package/src/cloud/cloud-manager.test.ts +223 -0
  93. package/src/cloud/cloud-manager.ts +151 -0
  94. package/src/cloud/cloud-proxy.test.ts +122 -0
  95. package/src/cloud/cloud-proxy.ts +52 -0
  96. package/src/cloud/index.ts +23 -0
  97. package/src/cloud/reconnect.test.ts +178 -0
  98. package/src/cloud/reconnect.ts +108 -0
  99. package/src/cloud/validate-url.test.ts +147 -0
  100. package/src/cloud/validate-url.ts +176 -0
  101. package/src/config/character-schema.ts +44 -0
  102. package/src/config/config.ts +149 -0
  103. package/src/config/env-vars.ts +86 -0
  104. package/src/config/includes.ts +196 -0
  105. package/src/config/index.ts +15 -0
  106. package/src/config/object-utils.ts +10 -0
  107. package/src/config/paths.ts +92 -0
  108. package/src/config/plugin-auto-enable.ts +520 -0
  109. package/src/config/schema.ts +1342 -0
  110. package/src/config/telegram-custom-commands.ts +99 -0
  111. package/src/config/types.agent-defaults.ts +342 -0
  112. package/src/config/types.agents.ts +112 -0
  113. package/src/config/types.gateway.ts +243 -0
  114. package/src/config/types.hooks.ts +124 -0
  115. package/src/config/types.messages.ts +201 -0
  116. package/src/config/types.milady.ts +791 -0
  117. package/src/config/types.tools.ts +416 -0
  118. package/src/config/types.ts +7 -0
  119. package/src/config/zod-schema.agent-runtime.ts +777 -0
  120. package/src/config/zod-schema.core.ts +778 -0
  121. package/src/config/zod-schema.hooks.ts +139 -0
  122. package/src/config/zod-schema.providers-core.ts +1126 -0
  123. package/src/config/zod-schema.session.ts +98 -0
  124. package/src/config/zod-schema.ts +865 -0
  125. package/src/contracts/apps.ts +46 -0
  126. package/src/contracts/awareness.ts +56 -0
  127. package/src/contracts/config.ts +172 -0
  128. package/src/contracts/drop.ts +21 -0
  129. package/src/contracts/index.ts +8 -0
  130. package/src/contracts/onboarding.ts +592 -0
  131. package/src/contracts/permissions.ts +52 -0
  132. package/src/contracts/verification.ts +9 -0
  133. package/src/contracts/wallet.ts +503 -0
  134. package/src/diagnostics/integration-observability.ts +132 -0
  135. package/src/emotes/catalog.ts +655 -0
  136. package/src/external-modules.d.ts +7 -0
  137. package/src/hooks/discovery.test.ts +357 -0
  138. package/src/hooks/discovery.ts +231 -0
  139. package/src/hooks/eligibility.ts +146 -0
  140. package/src/hooks/hooks.test.ts +320 -0
  141. package/src/hooks/index.ts +8 -0
  142. package/src/hooks/loader.test.ts +418 -0
  143. package/src/hooks/loader.ts +256 -0
  144. package/src/hooks/registry.test.ts +168 -0
  145. package/src/hooks/registry.ts +74 -0
  146. package/src/hooks/types.ts +121 -0
  147. package/src/index.ts +19 -0
  148. package/src/onboarding-presets.ts +828 -0
  149. package/src/plugins/custom-rtmp/index.ts +40 -0
  150. package/src/providers/admin-trust.ts +76 -0
  151. package/src/providers/session-bridge.ts +143 -0
  152. package/src/providers/session-utils.ts +42 -0
  153. package/src/providers/simple-mode.ts +113 -0
  154. package/src/providers/ui-catalog.ts +135 -0
  155. package/src/providers/workspace-provider.ts +213 -0
  156. package/src/providers/workspace.ts +497 -0
  157. package/src/runtime/agent-event-service.ts +57 -0
  158. package/src/runtime/cloud-onboarding.test.ts +489 -0
  159. package/src/runtime/cloud-onboarding.ts +408 -0
  160. package/src/runtime/core-plugins.ts +53 -0
  161. package/src/runtime/custom-actions.ts +605 -0
  162. package/src/runtime/eliza.ts +4941 -0
  163. package/src/runtime/embedding-presets.ts +73 -0
  164. package/src/runtime/index.ts +8 -0
  165. package/src/runtime/milady-plugin.ts +180 -0
  166. package/src/runtime/onboarding-names.ts +76 -0
  167. package/src/runtime/release-plugin-policy.ts +119 -0
  168. package/src/runtime/restart.ts +59 -0
  169. package/src/runtime/trajectory-persistence.ts +2584 -0
  170. package/src/runtime/version.ts +6 -0
  171. package/src/security/audit-log.ts +222 -0
  172. package/src/security/network-policy.ts +91 -0
  173. package/src/server/index.ts +6 -0
  174. package/src/services/agent-export.ts +976 -0
  175. package/src/services/app-manager.ts +755 -0
  176. package/src/services/browser-capture.ts +215 -0
  177. package/src/services/coding-agent-context.ts +355 -0
  178. package/src/services/fallback-training-service.ts +196 -0
  179. package/src/services/index.ts +17 -0
  180. package/src/services/mcp-marketplace.ts +327 -0
  181. package/src/services/plugin-manager-types.ts +185 -0
  182. package/src/services/privy-wallets.ts +352 -0
  183. package/src/services/registry-client-app-meta.ts +201 -0
  184. package/src/services/registry-client-endpoints.ts +253 -0
  185. package/src/services/registry-client-local.ts +485 -0
  186. package/src/services/registry-client-network.ts +173 -0
  187. package/src/services/registry-client-queries.ts +176 -0
  188. package/src/services/registry-client-types.ts +104 -0
  189. package/src/services/registry-client.ts +366 -0
  190. package/src/services/remote-signing-service.ts +261 -0
  191. package/src/services/sandbox-engine.ts +753 -0
  192. package/src/services/sandbox-manager.ts +503 -0
  193. package/src/services/self-updater.ts +213 -0
  194. package/src/services/signal-pairing.ts +189 -0
  195. package/src/services/signing-policy.ts +230 -0
  196. package/src/services/skill-catalog-client.ts +195 -0
  197. package/src/services/skill-marketplace.ts +909 -0
  198. package/src/services/stream-manager.ts +707 -0
  199. package/src/services/tts-stream-bridge.ts +465 -0
  200. package/src/services/update-checker.ts +163 -0
  201. package/src/services/version-compat.ts +367 -0
  202. package/src/services/whatsapp-pairing.ts +279 -0
  203. package/src/shared/ui-catalog-prompt.ts +1158 -0
  204. package/src/test-support/process-helpers.ts +35 -0
  205. package/src/test-support/route-test-helpers.ts +113 -0
  206. package/src/test-support/test-helpers.ts +304 -0
  207. package/src/testing/index.ts +3 -0
  208. package/src/triggers/action.ts +342 -0
  209. package/src/triggers/runtime.ts +432 -0
  210. package/src/triggers/scheduling.ts +472 -0
  211. package/src/triggers/types.ts +133 -0
  212. package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
  213. package/src/types/external-modules.d.ts +7 -0
  214. package/src/utils/exec-safety.ts +23 -0
  215. package/src/utils/number-parsing.ts +112 -0
  216. package/src/utils/spoken-text.ts +65 -0
  217. package/src/version-resolver.ts +60 -0
  218. package/test/api/agent-admin-routes.test.ts +160 -0
  219. package/test/api/agent-lifecycle-routes.test.ts +164 -0
  220. package/test/api/agent-transfer-routes.test.ts +136 -0
  221. package/test/api/apps-routes.test.ts +140 -0
  222. package/test/api/auth-routes.test.ts +160 -0
  223. package/test/api/bug-report-routes.test.ts +88 -0
  224. package/test/api/knowledge-routes.test.ts +73 -0
  225. package/test/api/lifecycle.test.ts +342 -0
  226. package/test/api/memory-routes.test.ts +74 -0
  227. package/test/api/models-routes.test.ts +112 -0
  228. package/test/api/nfa-routes.test.ts +78 -0
  229. package/test/api/permissions-routes.test.ts +185 -0
  230. package/test/api/registry-routes.test.ts +157 -0
  231. package/test/api/signal-routes.test.ts +113 -0
  232. package/test/api/subscription-routes.test.ts +90 -0
  233. package/test/api/trigger-routes.test.ts +87 -0
  234. package/test/api/wallet-routes.observability.test.ts +191 -0
  235. package/test/api/wallet-routes.test.ts +502 -0
  236. package/test/diagnostics/integration-observability.test.ts +135 -0
  237. package/test/security/audit-log.test.ts +229 -0
  238. package/test/security/network-policy.test.ts +143 -0
  239. package/test/services/version-compat.test.ts +127 -0
  240. package/tsconfig.build.json +21 -0
  241. package/tsconfig.json +19 -0
@@ -0,0 +1,161 @@
1
+ import os from "node:os";
2
+ import type { RouteRequestContext } from "./route-helpers";
3
+
4
+ export const BUG_REPORT_REPO = "milady-ai/milady";
5
+ const GITHUB_ISSUES_URL = `https://api.github.com/repos/${BUG_REPORT_REPO}/issues`;
6
+ const GITHUB_NEW_ISSUE_URL = `https://github.com/${BUG_REPORT_REPO}/issues/new?template=bug_report.yml`;
7
+
8
+ const BUG_REPORT_WINDOW_MS = 10 * 60 * 1000;
9
+ const BUG_REPORT_MAX_SUBMISSIONS = 5;
10
+ const bugReportAttempts = new Map<string, { count: number; resetAt: number }>();
11
+
12
+ function sweepExpiredEntries(
13
+ map: Map<string, { count: number; resetAt: number }>,
14
+ now: number,
15
+ threshold: number,
16
+ ): void {
17
+ if (map.size <= threshold) return;
18
+ for (const [key, value] of map) {
19
+ if (now > value.resetAt) map.delete(key);
20
+ }
21
+ }
22
+
23
+ export function rateLimitBugReport(ip: string | null): boolean {
24
+ const key = ip ?? "unknown";
25
+ const now = Date.now();
26
+ sweepExpiredEntries(bugReportAttempts, now, 100);
27
+ const current = bugReportAttempts.get(key);
28
+ if (!current || now > current.resetAt) {
29
+ bugReportAttempts.set(key, {
30
+ count: 1,
31
+ resetAt: now + BUG_REPORT_WINDOW_MS,
32
+ });
33
+ return true;
34
+ }
35
+ if (current.count >= BUG_REPORT_MAX_SUBMISSIONS) return false;
36
+ current.count += 1;
37
+ return true;
38
+ }
39
+
40
+ export function resetBugReportRateLimit(): void {
41
+ bugReportAttempts.clear();
42
+ }
43
+
44
+ interface BugReportBody {
45
+ description: string;
46
+ stepsToReproduce: string;
47
+ expectedBehavior?: string;
48
+ actualBehavior?: string;
49
+ environment?: string;
50
+ nodeVersion?: string;
51
+ modelProvider?: string;
52
+ logs?: string;
53
+ }
54
+
55
+ export function sanitize(input: string, maxLen = 10_000): string {
56
+ return input.replace(/<[^>]*>/g, "").slice(0, maxLen);
57
+ }
58
+
59
+ function formatIssueBody(body: BugReportBody): string {
60
+ const sections: string[] = [];
61
+ sections.push(`### Description\n\n${sanitize(body.description)}`);
62
+ sections.push(`### Steps to Reproduce\n\n${sanitize(body.stepsToReproduce)}`);
63
+ if (body.expectedBehavior) {
64
+ sections.push(
65
+ `### Expected Behavior\n\n${sanitize(body.expectedBehavior)}`,
66
+ );
67
+ }
68
+ if (body.actualBehavior) {
69
+ sections.push(`### Actual Behavior\n\n${sanitize(body.actualBehavior)}`);
70
+ }
71
+ if (body.environment) {
72
+ sections.push(`### Environment\n\n${sanitize(body.environment, 200)}`);
73
+ }
74
+ if (body.nodeVersion) {
75
+ sections.push(`### Node Version\n\n${sanitize(body.nodeVersion, 200)}`);
76
+ }
77
+ if (body.modelProvider) {
78
+ sections.push(`### Model Provider\n\n${sanitize(body.modelProvider, 200)}`);
79
+ }
80
+ if (body.logs) {
81
+ sections.push(`### Logs\n\n\`\`\`\n${sanitize(body.logs, 50_000)}\n\`\`\``);
82
+ }
83
+ return sections.join("\n\n");
84
+ }
85
+
86
+ export async function handleBugReportRoutes(
87
+ ctx: RouteRequestContext,
88
+ ): Promise<boolean> {
89
+ const { req, res, method, pathname, json, error, readJsonBody } = ctx;
90
+
91
+ if (method === "GET" && pathname === "/api/bug-report/info") {
92
+ json(res, {
93
+ nodeVersion: process.version,
94
+ platform: os.platform(),
95
+ });
96
+ return true;
97
+ }
98
+
99
+ if (method === "POST" && pathname === "/api/bug-report") {
100
+ if (!rateLimitBugReport(req.socket.remoteAddress ?? null)) {
101
+ error(res, "Too many bug reports. Try again later.", 429);
102
+ return true;
103
+ }
104
+
105
+ const body = await readJsonBody<BugReportBody>(req, res);
106
+ if (!body) return true;
107
+
108
+ if (!body.description?.trim() || !body.stepsToReproduce?.trim()) {
109
+ error(res, "description and stepsToReproduce are required", 400);
110
+ return true;
111
+ }
112
+
113
+ const githubToken = process.env.GITHUB_TOKEN;
114
+ if (!githubToken) {
115
+ json(res, { fallback: GITHUB_NEW_ISSUE_URL });
116
+ return true;
117
+ }
118
+
119
+ try {
120
+ const sanitizedTitle = sanitize(body.description, 80).replace(
121
+ /[\r\n]+/g,
122
+ " ",
123
+ );
124
+ const issueBody = formatIssueBody(body);
125
+ const issueRes = await fetch(GITHUB_ISSUES_URL, {
126
+ method: "POST",
127
+ headers: {
128
+ Authorization: `Bearer ${githubToken}`,
129
+ Accept: "application/vnd.github.v3+json",
130
+ "Content-Type": "application/json",
131
+ },
132
+ body: JSON.stringify({
133
+ title: `[Bug] ${sanitizedTitle}`,
134
+ body: issueBody,
135
+ labels: ["bug", "triage", "user-reported"],
136
+ }),
137
+ });
138
+
139
+ if (!issueRes.ok) {
140
+ error(res, `GitHub API error (${issueRes.status})`, 502);
141
+ return true;
142
+ }
143
+
144
+ const issueData = (await issueRes.json()) as { html_url?: string };
145
+ const url = issueData.html_url;
146
+ if (
147
+ typeof url !== "string" ||
148
+ !url.startsWith(`https://github.com/${BUG_REPORT_REPO}/issues/`)
149
+ ) {
150
+ error(res, "Unexpected response from GitHub API", 502);
151
+ return true;
152
+ }
153
+ json(res, { url });
154
+ } catch {
155
+ error(res, "Failed to create GitHub issue", 500);
156
+ }
157
+ return true;
158
+ }
159
+
160
+ return false;
161
+ }
@@ -0,0 +1,421 @@
1
+ import type { AgentRuntime } from "@elizaos/core";
2
+ import { logger, ModelType } from "@elizaos/core";
3
+ import type { RouteRequestContext } from "./route-helpers";
4
+
5
+ interface CharacterGenerateContext {
6
+ name?: string;
7
+ system?: string;
8
+ bio?: string;
9
+ topics?: string[];
10
+ style?: { all?: string[]; chat?: string[]; post?: string[] };
11
+ postExamples?: string[];
12
+ }
13
+
14
+ type CharacterGenerateField =
15
+ | "bio"
16
+ | "system"
17
+ | "style"
18
+ | "chatExamples"
19
+ | "postExamples";
20
+ type CharacterGenerateMode = "append" | "replace";
21
+
22
+ interface AgentConfigLike {
23
+ id?: string;
24
+ default?: boolean;
25
+ name?: string;
26
+ bio?: string[];
27
+ system?: string;
28
+ adjectives?: string[];
29
+ topics?: string[];
30
+ style?: {
31
+ all?: string[];
32
+ chat?: string[];
33
+ post?: string[];
34
+ };
35
+ messageExamples?: unknown;
36
+ postExamples?: string[];
37
+ }
38
+
39
+ interface AutonomousConfigLike {
40
+ agents?: {
41
+ list?: AgentConfigLike[];
42
+ };
43
+ }
44
+
45
+ interface CharacterParseIssueLike {
46
+ path: Array<string | number>;
47
+ message: string;
48
+ }
49
+
50
+ interface CharacterParseErrorLike {
51
+ issues: CharacterParseIssueLike[];
52
+ }
53
+
54
+ type CharacterValidationResult =
55
+ | { success: true }
56
+ | { success: false; error: CharacterParseErrorLike };
57
+
58
+ export interface CharacterRouteState {
59
+ runtime: AgentRuntime | null;
60
+ agentName: string;
61
+ config?: AutonomousConfigLike;
62
+ }
63
+
64
+ export interface CharacterRouteContext extends RouteRequestContext {
65
+ state: CharacterRouteState;
66
+ pickRandomNames: (count: number) => string[];
67
+ saveConfig?: (config: AutonomousConfigLike) => void;
68
+ validateCharacter: (
69
+ body: Record<string, unknown>,
70
+ ) => CharacterValidationResult;
71
+ }
72
+
73
+ function syncRuntimeCharacterToConfig(
74
+ state: CharacterRouteState,
75
+ saveConfig?: (config: AutonomousConfigLike) => void,
76
+ ): void {
77
+ const runtime = state.runtime;
78
+ const config = state.config;
79
+ if (!runtime || !config) return;
80
+
81
+ if (!config.agents) config.agents = {};
82
+ const existingList = config.agents.list ?? [];
83
+ const primaryAgent: AgentConfigLike = existingList[0] ?? {
84
+ id: "main",
85
+ default: true,
86
+ };
87
+ const character = runtime.character;
88
+ const nextAgent: AgentConfigLike = {
89
+ ...primaryAgent,
90
+ ...(character.name ? { name: character.name } : {}),
91
+ ...(Array.isArray(character.bio) ? { bio: [...character.bio] } : {}),
92
+ ...(typeof character.system === "string"
93
+ ? { system: character.system }
94
+ : {}),
95
+ ...(Array.isArray(character.adjectives)
96
+ ? { adjectives: [...character.adjectives] }
97
+ : {}),
98
+ ...(Array.isArray((character as { topics?: string[] }).topics)
99
+ ? { topics: [...((character as { topics?: string[] }).topics ?? [])] }
100
+ : {}),
101
+ ...(character.style
102
+ ? {
103
+ style: {
104
+ ...(Array.isArray(character.style.all)
105
+ ? { all: [...character.style.all] }
106
+ : {}),
107
+ ...(Array.isArray(character.style.chat)
108
+ ? { chat: [...character.style.chat] }
109
+ : {}),
110
+ ...(Array.isArray(character.style.post)
111
+ ? { post: [...character.style.post] }
112
+ : {}),
113
+ },
114
+ }
115
+ : {}),
116
+ ...(Array.isArray(character.postExamples)
117
+ ? { postExamples: [...character.postExamples] }
118
+ : {}),
119
+ ...(Array.isArray(character.messageExamples)
120
+ ? {
121
+ messageExamples: JSON.parse(
122
+ JSON.stringify(character.messageExamples),
123
+ ),
124
+ }
125
+ : {}),
126
+ };
127
+
128
+ config.agents.list = [nextAgent, ...existingList.slice(1)];
129
+ saveConfig?.(config);
130
+ }
131
+
132
+ function buildCharacterSummary(ctx: CharacterGenerateContext): string {
133
+ return [
134
+ ctx.name ? `Name: ${ctx.name}` : "",
135
+ ctx.system ? `System prompt: ${ctx.system}` : "",
136
+ ctx.bio ? `Bio: ${ctx.bio}` : "",
137
+ ctx.topics?.length ? `Topics: ${ctx.topics.join(", ")}` : "",
138
+ ctx.style?.all?.length ? `Style rules: ${ctx.style.all.join("; ")}` : "",
139
+ ]
140
+ .filter(Boolean)
141
+ .join("\n");
142
+ }
143
+
144
+ function buildGeneratePrompt(
145
+ field: CharacterGenerateField,
146
+ context: CharacterGenerateContext,
147
+ mode: CharacterGenerateMode | undefined,
148
+ ): string {
149
+ const charSummary = buildCharacterSummary(context);
150
+
151
+ if (field === "bio") {
152
+ return `Given this character:\n${charSummary}\n\nWrite a concise, compelling bio for this character (3-4 short paragraphs, one per line). Use their current interests and info to expand the bio into something more interesting and unique. Just output the bio lines, nothing else. Match the character's voice and personality.`;
153
+ }
154
+
155
+ if (field === "system") {
156
+ return `Given this character:\n${charSummary}\n\nWrite a system prompt that defines how this AI agent should behave. The system prompt should be written in first person, describing the agent's personality, communication style, and core behaviors. Include specific guidelines about tone, language style (formal/casual), and any unique quirks. Keep it concise but comprehensive (2-4 paragraphs). Just output the system prompt text, nothing else.`;
157
+ }
158
+
159
+ if (field === "style") {
160
+ const existing =
161
+ mode === "append" && context.style?.all?.length
162
+ ? `\nExisting style rules (add to these, don't repeat):\n${context.style.all.join("\n")}`
163
+ : "";
164
+ return `Given this character:\n${charSummary}${existing}\n\nGenerate 4-6 communication style rules for this character. Output a JSON object with keys "all", "chat", "post", each containing an array of short rule strings. Just output the JSON, nothing else.`;
165
+ }
166
+
167
+ if (field === "chatExamples") {
168
+ return `Given this character:\n${charSummary}\n\nGenerate 3 example chat conversations showing how this character responds. Output a JSON array where each element is an array of message objects like [{"user":"{{user1}}","content":{"text":"..."}},{"user":"{{agentName}}","content":{"text":"..."}}]. Just output the JSON array, nothing else.`;
169
+ }
170
+
171
+ const existing =
172
+ mode === "append" && context.postExamples?.length
173
+ ? `\nExisting posts (add new ones, don't repeat):\n${context.postExamples.join("\n")}`
174
+ : "";
175
+ return `Given this character:\n${charSummary}${existing}\n\nGenerate 3-5 example social media posts this character would write. Output a JSON array of strings. Just output the JSON array, nothing else.`;
176
+ }
177
+
178
+ const CHARACTER_SCHEMA_FIELDS = [
179
+ {
180
+ key: "name",
181
+ type: "string",
182
+ label: "Name",
183
+ description: "Agent display name",
184
+ maxLength: 100,
185
+ },
186
+ {
187
+ key: "username",
188
+ type: "string",
189
+ label: "Username",
190
+ description: "Agent username for platforms",
191
+ maxLength: 50,
192
+ },
193
+ {
194
+ key: "bio",
195
+ type: "string | string[]",
196
+ label: "Bio",
197
+ description: "Biography — single string or array of points",
198
+ },
199
+ {
200
+ key: "system",
201
+ type: "string",
202
+ label: "System Prompt",
203
+ description: "System prompt defining core behavior",
204
+ maxLength: 10000,
205
+ },
206
+ {
207
+ key: "adjectives",
208
+ type: "string[]",
209
+ label: "Adjectives",
210
+ description: "Personality adjectives (e.g. curious, witty)",
211
+ },
212
+ {
213
+ key: "topics",
214
+ type: "string[]",
215
+ label: "Topics",
216
+ description: "Conversation topics the agent is knowledgeable about",
217
+ },
218
+ {
219
+ key: "style",
220
+ type: "object",
221
+ label: "Style",
222
+ description: "Communication style guides",
223
+ children: [
224
+ {
225
+ key: "all",
226
+ type: "string[]",
227
+ label: "All",
228
+ description: "Style guidelines for all responses",
229
+ },
230
+ {
231
+ key: "chat",
232
+ type: "string[]",
233
+ label: "Chat",
234
+ description: "Style guidelines for chat responses",
235
+ },
236
+ {
237
+ key: "post",
238
+ type: "string[]",
239
+ label: "Post",
240
+ description: "Style guidelines for social media posts",
241
+ },
242
+ ],
243
+ },
244
+ {
245
+ key: "messageExamples",
246
+ type: "array",
247
+ label: "Message Examples",
248
+ description: "Example conversations demonstrating the agent's voice",
249
+ },
250
+ {
251
+ key: "postExamples",
252
+ type: "string[]",
253
+ label: "Post Examples",
254
+ description: "Example social media posts",
255
+ },
256
+ ] as const;
257
+
258
+ export async function handleCharacterRoutes(
259
+ ctx: CharacterRouteContext,
260
+ ): Promise<boolean> {
261
+ const {
262
+ req,
263
+ res,
264
+ method,
265
+ pathname,
266
+ state,
267
+ saveConfig,
268
+ readJsonBody,
269
+ json,
270
+ error,
271
+ pickRandomNames,
272
+ validateCharacter,
273
+ } = ctx;
274
+
275
+ if (method === "GET" && pathname === "/api/character") {
276
+ const runtime = state.runtime;
277
+ const merged: Record<string, unknown> = {};
278
+ if (runtime) {
279
+ const character = runtime.character;
280
+ if (character.name) merged.name = character.name;
281
+ if (character.username) merged.username = character.username;
282
+ if (character.bio) merged.bio = character.bio;
283
+ if (character.system) merged.system = character.system;
284
+ if (character.adjectives) merged.adjectives = character.adjectives;
285
+ if (Array.isArray((character as { topics?: string[] }).topics)) {
286
+ merged.topics = (character as { topics?: string[] }).topics;
287
+ }
288
+ if (character.style) merged.style = character.style;
289
+ if (character.messageExamples) {
290
+ merged.messageExamples = character.messageExamples;
291
+ }
292
+ if (character.postExamples) {
293
+ merged.postExamples = character.postExamples;
294
+ }
295
+ }
296
+
297
+ json(res, { character: merged, agentName: state.agentName });
298
+ return true;
299
+ }
300
+
301
+ if (method === "PUT" && pathname === "/api/character") {
302
+ const body = await readJsonBody<Record<string, unknown>>(req, res);
303
+ if (!body) return true;
304
+
305
+ const result = validateCharacter(body);
306
+ if (!result.success) {
307
+ const issues = result.error.issues.map((issue) => ({
308
+ path: issue.path.join("."),
309
+ message: issue.message,
310
+ }));
311
+ json(res, { ok: false, validationErrors: issues }, 422);
312
+ return true;
313
+ }
314
+
315
+ if (state.runtime) {
316
+ const character = state.runtime.character;
317
+ if (body.name != null) character.name = String(body.name);
318
+ if (body.username != null) character.username = String(body.username);
319
+ if (body.bio != null) {
320
+ character.bio = Array.isArray(body.bio)
321
+ ? (body.bio as string[])
322
+ : [String(body.bio)];
323
+ }
324
+ if (body.system != null) character.system = String(body.system);
325
+ if (body.adjectives != null) {
326
+ character.adjectives = body.adjectives as string[];
327
+ }
328
+ if (body.topics != null) {
329
+ (character as { topics?: string[] }).topics = body.topics as string[];
330
+ }
331
+ if (body.style != null) {
332
+ character.style = body.style as NonNullable<typeof character.style>;
333
+ }
334
+ if (body.messageExamples != null) {
335
+ character.messageExamples = body.messageExamples as NonNullable<
336
+ typeof character.messageExamples
337
+ >;
338
+ }
339
+ if (body.postExamples != null) {
340
+ character.postExamples = body.postExamples as string[];
341
+ }
342
+ }
343
+
344
+ try {
345
+ syncRuntimeCharacterToConfig(state, saveConfig);
346
+ } catch (err) {
347
+ error(
348
+ res,
349
+ err instanceof Error
350
+ ? `Failed to persist character: ${err.message}`
351
+ : "Failed to persist character",
352
+ 500,
353
+ );
354
+ return true;
355
+ }
356
+
357
+ if (body.name) state.agentName = String(body.name);
358
+ json(res, { ok: true, character: body, agentName: state.agentName });
359
+ return true;
360
+ }
361
+
362
+ if (method === "GET" && pathname === "/api/character/random-name") {
363
+ const names = pickRandomNames(1);
364
+ json(res, { name: names[0] ?? "Reimu" });
365
+ return true;
366
+ }
367
+
368
+ if (method === "POST" && pathname === "/api/character/generate") {
369
+ const body = await readJsonBody<{
370
+ field: CharacterGenerateField;
371
+ context: CharacterGenerateContext;
372
+ mode?: CharacterGenerateMode;
373
+ }>(req, res);
374
+ if (!body) return true;
375
+
376
+ if (!body.field || !body.context) {
377
+ error(res, "field and context are required", 400);
378
+ return true;
379
+ }
380
+
381
+ const runtime = state.runtime;
382
+ if (!runtime) {
383
+ error(res, "Agent runtime not available. Start the agent first.", 503);
384
+ return true;
385
+ }
386
+
387
+ if (
388
+ body.field !== "bio" &&
389
+ body.field !== "system" &&
390
+ body.field !== "style" &&
391
+ body.field !== "chatExamples" &&
392
+ body.field !== "postExamples"
393
+ ) {
394
+ error(res, `Unknown field: ${body.field}`, 400);
395
+ return true;
396
+ }
397
+
398
+ const prompt = buildGeneratePrompt(body.field, body.context, body.mode);
399
+
400
+ try {
401
+ const result = await runtime.useModel(ModelType.TEXT_SMALL, {
402
+ prompt,
403
+ temperature: 0.8,
404
+ maxTokens: 1500,
405
+ });
406
+ json(res, { generated: String(result) });
407
+ } catch (err) {
408
+ const message = err instanceof Error ? err.message : "generation failed";
409
+ logger.error(`[character-generate] ${message}`);
410
+ error(res, message, 500);
411
+ }
412
+ return true;
413
+ }
414
+
415
+ if (method === "GET" && pathname === "/api/character/schema") {
416
+ json(res, { fields: CHARACTER_SCHEMA_FIELDS });
417
+ return true;
418
+ }
419
+
420
+ return false;
421
+ }