@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.
- package/LICENSE +21 -0
- package/package.json +270 -0
- package/src/actions/emote.ts +101 -0
- package/src/actions/restart.ts +101 -0
- package/src/actions/send-message.ts +168 -0
- package/src/actions/stream-control.ts +439 -0
- package/src/actions/switch-stream-source.ts +126 -0
- package/src/actions/terminal.ts +186 -0
- package/src/api/agent-admin-routes.ts +178 -0
- package/src/api/agent-lifecycle-routes.ts +129 -0
- package/src/api/agent-model.ts +143 -0
- package/src/api/agent-transfer-routes.ts +211 -0
- package/src/api/apps-routes.ts +210 -0
- package/src/api/auth-routes.ts +90 -0
- package/src/api/bsc-trade.ts +736 -0
- package/src/api/bug-report-routes.ts +161 -0
- package/src/api/character-routes.ts +421 -0
- package/src/api/cloud-billing-routes.ts +598 -0
- package/src/api/cloud-compat-routes.ts +192 -0
- package/src/api/cloud-routes.ts +529 -0
- package/src/api/cloud-status-routes.ts +234 -0
- package/src/api/compat-utils.ts +154 -0
- package/src/api/connector-health.ts +135 -0
- package/src/api/coordinator-wiring.ts +179 -0
- package/src/api/credit-detection.ts +47 -0
- package/src/api/database.ts +1357 -0
- package/src/api/diagnostics-routes.ts +389 -0
- package/src/api/drop-service.ts +205 -0
- package/src/api/early-logs.ts +111 -0
- package/src/api/http-helpers.ts +252 -0
- package/src/api/index.ts +85 -0
- package/src/api/knowledge-routes.ts +1189 -0
- package/src/api/knowledge-service-loader.ts +92 -0
- package/src/api/memory-bounds.ts +121 -0
- package/src/api/memory-routes.ts +349 -0
- package/src/api/merkle-tree.ts +239 -0
- package/src/api/models-routes.ts +72 -0
- package/src/api/nfa-routes.ts +169 -0
- package/src/api/nft-verify.ts +188 -0
- package/src/api/og-tracker.ts +72 -0
- package/src/api/parse-action-block.ts +145 -0
- package/src/api/permissions-routes.ts +222 -0
- package/src/api/plugin-validation.ts +355 -0
- package/src/api/provider-switch-config.ts +455 -0
- package/src/api/registry-routes.ts +165 -0
- package/src/api/registry-service.ts +292 -0
- package/src/api/route-helpers.ts +21 -0
- package/src/api/sandbox-routes.ts +1480 -0
- package/src/api/server.ts +17674 -0
- package/src/api/signal-routes.ts +265 -0
- package/src/api/stream-persistence.ts +297 -0
- package/src/api/stream-route-state.ts +48 -0
- package/src/api/stream-routes.ts +1046 -0
- package/src/api/stream-voice-routes.ts +208 -0
- package/src/api/streaming-text.ts +129 -0
- package/src/api/streaming-types.ts +23 -0
- package/src/api/subscription-routes.ts +283 -0
- package/src/api/terminal-run-limits.ts +31 -0
- package/src/api/training-backend-check.ts +40 -0
- package/src/api/training-routes.ts +314 -0
- package/src/api/training-service-like.ts +46 -0
- package/src/api/trajectory-routes.ts +714 -0
- package/src/api/trigger-routes.ts +438 -0
- package/src/api/twitter-verify.ts +226 -0
- package/src/api/tx-service.ts +193 -0
- package/src/api/wallet-dex-prices.ts +206 -0
- package/src/api/wallet-evm-balance.ts +989 -0
- package/src/api/wallet-routes.ts +505 -0
- package/src/api/wallet-rpc.ts +523 -0
- package/src/api/wallet-trading-profile.ts +694 -0
- package/src/api/wallet.ts +745 -0
- package/src/api/whatsapp-routes.ts +282 -0
- package/src/api/zip-utils.ts +130 -0
- package/src/auth/anthropic.ts +63 -0
- package/src/auth/apply-stealth.ts +38 -0
- package/src/auth/claude-code-stealth.ts +141 -0
- package/src/auth/credentials.ts +226 -0
- package/src/auth/index.ts +18 -0
- package/src/auth/openai-codex.ts +94 -0
- package/src/auth/types.ts +24 -0
- package/src/awareness/registry.ts +220 -0
- package/src/bin.ts +10 -0
- package/src/cli/index.ts +36 -0
- package/src/cli/parse-duration.ts +43 -0
- package/src/cloud/auth.test.ts +370 -0
- package/src/cloud/auth.ts +176 -0
- package/src/cloud/backup.test.ts +150 -0
- package/src/cloud/backup.ts +50 -0
- package/src/cloud/base-url.ts +45 -0
- package/src/cloud/bridge-client.test.ts +481 -0
- package/src/cloud/bridge-client.ts +307 -0
- package/src/cloud/cloud-manager.test.ts +223 -0
- package/src/cloud/cloud-manager.ts +151 -0
- package/src/cloud/cloud-proxy.test.ts +122 -0
- package/src/cloud/cloud-proxy.ts +52 -0
- package/src/cloud/index.ts +23 -0
- package/src/cloud/reconnect.test.ts +178 -0
- package/src/cloud/reconnect.ts +108 -0
- package/src/cloud/validate-url.test.ts +147 -0
- package/src/cloud/validate-url.ts +176 -0
- package/src/config/character-schema.ts +44 -0
- package/src/config/config.ts +149 -0
- package/src/config/env-vars.ts +86 -0
- package/src/config/includes.ts +196 -0
- package/src/config/index.ts +15 -0
- package/src/config/object-utils.ts +10 -0
- package/src/config/paths.ts +92 -0
- package/src/config/plugin-auto-enable.ts +520 -0
- package/src/config/schema.ts +1342 -0
- package/src/config/telegram-custom-commands.ts +99 -0
- package/src/config/types.agent-defaults.ts +342 -0
- package/src/config/types.agents.ts +112 -0
- package/src/config/types.gateway.ts +243 -0
- package/src/config/types.hooks.ts +124 -0
- package/src/config/types.messages.ts +201 -0
- package/src/config/types.milady.ts +791 -0
- package/src/config/types.tools.ts +416 -0
- package/src/config/types.ts +7 -0
- package/src/config/zod-schema.agent-runtime.ts +777 -0
- package/src/config/zod-schema.core.ts +778 -0
- package/src/config/zod-schema.hooks.ts +139 -0
- package/src/config/zod-schema.providers-core.ts +1126 -0
- package/src/config/zod-schema.session.ts +98 -0
- package/src/config/zod-schema.ts +865 -0
- package/src/contracts/apps.ts +46 -0
- package/src/contracts/awareness.ts +56 -0
- package/src/contracts/config.ts +172 -0
- package/src/contracts/drop.ts +21 -0
- package/src/contracts/index.ts +8 -0
- package/src/contracts/onboarding.ts +592 -0
- package/src/contracts/permissions.ts +52 -0
- package/src/contracts/verification.ts +9 -0
- package/src/contracts/wallet.ts +503 -0
- package/src/diagnostics/integration-observability.ts +132 -0
- package/src/emotes/catalog.ts +655 -0
- package/src/external-modules.d.ts +7 -0
- package/src/hooks/discovery.test.ts +357 -0
- package/src/hooks/discovery.ts +231 -0
- package/src/hooks/eligibility.ts +146 -0
- package/src/hooks/hooks.test.ts +320 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/loader.test.ts +418 -0
- package/src/hooks/loader.ts +256 -0
- package/src/hooks/registry.test.ts +168 -0
- package/src/hooks/registry.ts +74 -0
- package/src/hooks/types.ts +121 -0
- package/src/index.ts +19 -0
- package/src/onboarding-presets.ts +828 -0
- package/src/plugins/custom-rtmp/index.ts +40 -0
- package/src/providers/admin-trust.ts +76 -0
- package/src/providers/session-bridge.ts +143 -0
- package/src/providers/session-utils.ts +42 -0
- package/src/providers/simple-mode.ts +113 -0
- package/src/providers/ui-catalog.ts +135 -0
- package/src/providers/workspace-provider.ts +213 -0
- package/src/providers/workspace.ts +497 -0
- package/src/runtime/agent-event-service.ts +57 -0
- package/src/runtime/cloud-onboarding.test.ts +489 -0
- package/src/runtime/cloud-onboarding.ts +408 -0
- package/src/runtime/core-plugins.ts +53 -0
- package/src/runtime/custom-actions.ts +605 -0
- package/src/runtime/eliza.ts +4941 -0
- package/src/runtime/embedding-presets.ts +73 -0
- package/src/runtime/index.ts +8 -0
- package/src/runtime/milady-plugin.ts +180 -0
- package/src/runtime/onboarding-names.ts +76 -0
- package/src/runtime/release-plugin-policy.ts +119 -0
- package/src/runtime/restart.ts +59 -0
- package/src/runtime/trajectory-persistence.ts +2584 -0
- package/src/runtime/version.ts +6 -0
- package/src/security/audit-log.ts +222 -0
- package/src/security/network-policy.ts +91 -0
- package/src/server/index.ts +6 -0
- package/src/services/agent-export.ts +976 -0
- package/src/services/app-manager.ts +755 -0
- package/src/services/browser-capture.ts +215 -0
- package/src/services/coding-agent-context.ts +355 -0
- package/src/services/fallback-training-service.ts +196 -0
- package/src/services/index.ts +17 -0
- package/src/services/mcp-marketplace.ts +327 -0
- package/src/services/plugin-manager-types.ts +185 -0
- package/src/services/privy-wallets.ts +352 -0
- package/src/services/registry-client-app-meta.ts +201 -0
- package/src/services/registry-client-endpoints.ts +253 -0
- package/src/services/registry-client-local.ts +485 -0
- package/src/services/registry-client-network.ts +173 -0
- package/src/services/registry-client-queries.ts +176 -0
- package/src/services/registry-client-types.ts +104 -0
- package/src/services/registry-client.ts +366 -0
- package/src/services/remote-signing-service.ts +261 -0
- package/src/services/sandbox-engine.ts +753 -0
- package/src/services/sandbox-manager.ts +503 -0
- package/src/services/self-updater.ts +213 -0
- package/src/services/signal-pairing.ts +189 -0
- package/src/services/signing-policy.ts +230 -0
- package/src/services/skill-catalog-client.ts +195 -0
- package/src/services/skill-marketplace.ts +909 -0
- package/src/services/stream-manager.ts +707 -0
- package/src/services/tts-stream-bridge.ts +465 -0
- package/src/services/update-checker.ts +163 -0
- package/src/services/version-compat.ts +367 -0
- package/src/services/whatsapp-pairing.ts +279 -0
- package/src/shared/ui-catalog-prompt.ts +1158 -0
- package/src/test-support/process-helpers.ts +35 -0
- package/src/test-support/route-test-helpers.ts +113 -0
- package/src/test-support/test-helpers.ts +304 -0
- package/src/testing/index.ts +3 -0
- package/src/triggers/action.ts +342 -0
- package/src/triggers/runtime.ts +432 -0
- package/src/triggers/scheduling.ts +472 -0
- package/src/triggers/types.ts +133 -0
- package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
- package/src/types/external-modules.d.ts +7 -0
- package/src/utils/exec-safety.ts +23 -0
- package/src/utils/number-parsing.ts +112 -0
- package/src/utils/spoken-text.ts +65 -0
- package/src/version-resolver.ts +60 -0
- package/test/api/agent-admin-routes.test.ts +160 -0
- package/test/api/agent-lifecycle-routes.test.ts +164 -0
- package/test/api/agent-transfer-routes.test.ts +136 -0
- package/test/api/apps-routes.test.ts +140 -0
- package/test/api/auth-routes.test.ts +160 -0
- package/test/api/bug-report-routes.test.ts +88 -0
- package/test/api/knowledge-routes.test.ts +73 -0
- package/test/api/lifecycle.test.ts +342 -0
- package/test/api/memory-routes.test.ts +74 -0
- package/test/api/models-routes.test.ts +112 -0
- package/test/api/nfa-routes.test.ts +78 -0
- package/test/api/permissions-routes.test.ts +185 -0
- package/test/api/registry-routes.test.ts +157 -0
- package/test/api/signal-routes.test.ts +113 -0
- package/test/api/subscription-routes.test.ts +90 -0
- package/test/api/trigger-routes.test.ts +87 -0
- package/test/api/wallet-routes.observability.test.ts +191 -0
- package/test/api/wallet-routes.test.ts +502 -0
- package/test/diagnostics/integration-observability.test.ts +135 -0
- package/test/security/audit-log.test.ts +229 -0
- package/test/security/network-policy.test.ts +143 -0
- package/test/services/version-compat.test.ts +127 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AgentRuntime, Memory, UUID } from "@elizaos/core";
|
|
2
|
+
|
|
3
|
+
export interface KnowledgeServiceLike {
|
|
4
|
+
addKnowledge(options: {
|
|
5
|
+
agentId?: UUID;
|
|
6
|
+
worldId: UUID;
|
|
7
|
+
roomId: UUID;
|
|
8
|
+
entityId: UUID;
|
|
9
|
+
clientDocumentId: UUID;
|
|
10
|
+
contentType: string;
|
|
11
|
+
originalFilename: string;
|
|
12
|
+
content: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
clientDocumentId: string;
|
|
16
|
+
storedDocumentMemoryId: UUID;
|
|
17
|
+
fragmentCount: number;
|
|
18
|
+
}>;
|
|
19
|
+
getKnowledge(
|
|
20
|
+
message: Memory,
|
|
21
|
+
scope?: { roomId?: UUID; worldId?: UUID; entityId?: UUID },
|
|
22
|
+
): Promise<
|
|
23
|
+
Array<{
|
|
24
|
+
id: UUID;
|
|
25
|
+
content: { text?: string };
|
|
26
|
+
similarity?: number;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
}>
|
|
29
|
+
>;
|
|
30
|
+
getMemories(params: {
|
|
31
|
+
tableName: string;
|
|
32
|
+
roomId?: UUID;
|
|
33
|
+
count?: number;
|
|
34
|
+
offset?: number;
|
|
35
|
+
end?: number;
|
|
36
|
+
}): Promise<Memory[]>;
|
|
37
|
+
countMemories(params: {
|
|
38
|
+
tableName: string;
|
|
39
|
+
roomId?: UUID;
|
|
40
|
+
unique?: boolean;
|
|
41
|
+
}): Promise<number>;
|
|
42
|
+
deleteMemory(memoryId: UUID): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type KnowledgeLoadFailReason =
|
|
46
|
+
| "timeout"
|
|
47
|
+
| "runtime_unavailable"
|
|
48
|
+
| "not_registered";
|
|
49
|
+
|
|
50
|
+
export interface KnowledgeServiceResult {
|
|
51
|
+
service: KnowledgeServiceLike | null;
|
|
52
|
+
reason?: KnowledgeLoadFailReason;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
56
|
+
const MAX_TIMEOUT_MS = 60_000;
|
|
57
|
+
|
|
58
|
+
export function getKnowledgeTimeoutMs(): number {
|
|
59
|
+
const envVal = process.env.KNOWLEDGE_SERVICE_TIMEOUT_MS;
|
|
60
|
+
if (!envVal) return DEFAULT_TIMEOUT_MS;
|
|
61
|
+
const parsed = Number.parseInt(envVal, 10);
|
|
62
|
+
if (Number.isNaN(parsed) || parsed <= 0) return DEFAULT_TIMEOUT_MS;
|
|
63
|
+
return Math.min(parsed, MAX_TIMEOUT_MS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function getKnowledgeService(
|
|
67
|
+
runtime: AgentRuntime | null,
|
|
68
|
+
): Promise<KnowledgeServiceResult> {
|
|
69
|
+
if (!runtime) {
|
|
70
|
+
return { service: null, reason: "runtime_unavailable" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let service = runtime.getService("knowledge") as KnowledgeServiceLike | null;
|
|
74
|
+
if (service) return { service };
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const servicePromise = runtime.getServiceLoadPromise("knowledge");
|
|
78
|
+
const timeoutMs = getKnowledgeTimeoutMs();
|
|
79
|
+
const timeout = new Promise<never>((_resolve, reject) => {
|
|
80
|
+
setTimeout(
|
|
81
|
+
() => reject(new Error("knowledge service timeout")),
|
|
82
|
+
timeoutMs,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
await Promise.race([servicePromise, timeout]);
|
|
86
|
+
service = runtime.getService("knowledge") as KnowledgeServiceLike | null;
|
|
87
|
+
if (service) return { service };
|
|
88
|
+
return { service: null, reason: "not_registered" };
|
|
89
|
+
} catch {
|
|
90
|
+
return { service: null, reason: "timeout" };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions for bounded in-memory data structures.
|
|
3
|
+
* Extracted from server.ts so they can be unit-tested in isolation.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Rate-limit map sweep ──────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
interface RateLimitEntry {
|
|
11
|
+
count: number;
|
|
12
|
+
resetAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Evict expired entries from a rate-limit map when it exceeds `threshold`.
|
|
17
|
+
* Safe to call during iteration (Map spec permits deletion during for-of).
|
|
18
|
+
*/
|
|
19
|
+
export function sweepExpiredEntries(
|
|
20
|
+
map: Map<string, RateLimitEntry>,
|
|
21
|
+
now: number,
|
|
22
|
+
threshold: number,
|
|
23
|
+
): void {
|
|
24
|
+
if (map.size <= threshold) return;
|
|
25
|
+
for (const [k, v] of map) {
|
|
26
|
+
if (now > v.resetAt) map.delete(k);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Conversation soft cap ─────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
interface ConversationLike {
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Evict the oldest conversation (by `updatedAt`) when the map exceeds `cap`.
|
|
38
|
+
* Returns the evicted key, or null if no eviction was needed.
|
|
39
|
+
*/
|
|
40
|
+
export function evictOldestConversation<T extends ConversationLike>(
|
|
41
|
+
map: Map<string, T>,
|
|
42
|
+
cap: number,
|
|
43
|
+
): string | null {
|
|
44
|
+
if (map.size <= cap) return null;
|
|
45
|
+
|
|
46
|
+
let oldestKey: string | null = null;
|
|
47
|
+
let oldestTime = Infinity;
|
|
48
|
+
for (const [k, v] of map) {
|
|
49
|
+
const t = new Date(v.updatedAt).getTime();
|
|
50
|
+
if (t < oldestTime) {
|
|
51
|
+
oldestTime = t;
|
|
52
|
+
oldestKey = k;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (oldestKey) map.delete(oldestKey);
|
|
56
|
+
return oldestKey;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Log buffer batch eviction ─────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Push an entry to a bounded buffer and batch-evict when the high-water
|
|
63
|
+
* mark is reached. Returns the current buffer length.
|
|
64
|
+
*
|
|
65
|
+
* @param buffer - The array to push into.
|
|
66
|
+
* @param entry - The item to append.
|
|
67
|
+
* @param highWater - Trigger eviction when `buffer.length` exceeds this.
|
|
68
|
+
* @param evictCount - Number of oldest entries to remove on eviction.
|
|
69
|
+
*/
|
|
70
|
+
export function pushWithBatchEvict<T>(
|
|
71
|
+
buffer: T[],
|
|
72
|
+
entry: T,
|
|
73
|
+
highWater: number,
|
|
74
|
+
evictCount: number,
|
|
75
|
+
): number {
|
|
76
|
+
buffer.push(entry);
|
|
77
|
+
if (buffer.length > highWater) {
|
|
78
|
+
buffer.splice(0, evictCount);
|
|
79
|
+
}
|
|
80
|
+
return buffer.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Static file cache ─────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
interface CachedFile {
|
|
86
|
+
body: Buffer;
|
|
87
|
+
mtimeMs: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Retrieve a file from a bounded cache, reading from disk on miss.
|
|
92
|
+
* Files larger than `fileSizeLimit` are never cached.
|
|
93
|
+
*
|
|
94
|
+
* @param cache - The Map serving as the LRU-ish cache.
|
|
95
|
+
* @param filePath - Absolute path to the file.
|
|
96
|
+
* @param mtimeMs - File's last-modified time (for invalidation).
|
|
97
|
+
* @param readFile - Callback that reads the file (injected for testing).
|
|
98
|
+
* @param maxEntries - Maximum number of cached files.
|
|
99
|
+
* @param fileSizeLimit - Maximum file size (bytes) eligible for caching.
|
|
100
|
+
*/
|
|
101
|
+
export function getOrReadCachedFile(
|
|
102
|
+
cache: Map<string, CachedFile>,
|
|
103
|
+
filePath: string,
|
|
104
|
+
mtimeMs: number,
|
|
105
|
+
readFile: (p: string) => Buffer,
|
|
106
|
+
maxEntries: number,
|
|
107
|
+
fileSizeLimit: number,
|
|
108
|
+
): Buffer {
|
|
109
|
+
const cached = cache.get(filePath);
|
|
110
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached.body;
|
|
111
|
+
|
|
112
|
+
const body = readFile(filePath);
|
|
113
|
+
if (body.length <= fileSizeLimit) {
|
|
114
|
+
if (cache.size >= maxEntries) {
|
|
115
|
+
const firstKey = cache.keys().next().value;
|
|
116
|
+
if (firstKey !== undefined) cache.delete(firstKey);
|
|
117
|
+
}
|
|
118
|
+
cache.set(filePath, { body, mtimeMs });
|
|
119
|
+
}
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
type AgentRuntime,
|
|
4
|
+
ChannelType,
|
|
5
|
+
createMessageMemory,
|
|
6
|
+
type Memory,
|
|
7
|
+
ModelType,
|
|
8
|
+
stringToUuid,
|
|
9
|
+
type UUID,
|
|
10
|
+
} from "@elizaos/core";
|
|
11
|
+
import { getKnowledgeService, type KnowledgeServiceLike } from "./knowledge-service-loader";
|
|
12
|
+
import { parsePositiveInteger } from "../utils/number-parsing";
|
|
13
|
+
import type { RouteRequestContext } from "./route-helpers";
|
|
14
|
+
|
|
15
|
+
const HASH_MEMORY_SOURCE = "hash_memory";
|
|
16
|
+
const MEMORY_SEARCH_SCAN_LIMIT = 500;
|
|
17
|
+
const MEMORY_SEARCH_DEFAULT_LIMIT = 10;
|
|
18
|
+
const MEMORY_SEARCH_MAX_LIMIT = 50;
|
|
19
|
+
const QUICK_CONTEXT_DEFAULT_LIMIT = 8;
|
|
20
|
+
const QUICK_CONTEXT_MAX_LIMIT = 20;
|
|
21
|
+
const QUICK_CONTEXT_KNOWLEDGE_THRESHOLD = 0.2;
|
|
22
|
+
|
|
23
|
+
export interface MemoryRouteContext extends RouteRequestContext {
|
|
24
|
+
url: URL;
|
|
25
|
+
runtime: AgentRuntime | null;
|
|
26
|
+
agentName: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type MemorySearchHit = {
|
|
30
|
+
id: string;
|
|
31
|
+
text: string;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
score: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type KnowledgeSearchHit = {
|
|
37
|
+
id: string;
|
|
38
|
+
text: string;
|
|
39
|
+
similarity: number;
|
|
40
|
+
documentId?: string;
|
|
41
|
+
documentTitle?: string;
|
|
42
|
+
position?: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function resolveAgentName(runtime: AgentRuntime, fallbackName: string): string {
|
|
46
|
+
return runtime.character.name?.trim() || fallbackName || "Milady";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function ensureMemoryConnection(
|
|
50
|
+
runtime: AgentRuntime,
|
|
51
|
+
agentName: string,
|
|
52
|
+
): Promise<{ roomId: UUID; entityId: UUID }> {
|
|
53
|
+
const entityId = runtime.agentId as UUID;
|
|
54
|
+
const roomId = stringToUuid(`${agentName}-hash-memory-room`) as UUID;
|
|
55
|
+
const worldId = stringToUuid(`${agentName}-hash-memory-world`) as UUID;
|
|
56
|
+
const messageServerId = stringToUuid(
|
|
57
|
+
`${agentName}-hash-memory-server`,
|
|
58
|
+
) as UUID;
|
|
59
|
+
|
|
60
|
+
await runtime.ensureConnection({
|
|
61
|
+
entityId,
|
|
62
|
+
roomId,
|
|
63
|
+
worldId,
|
|
64
|
+
userName: "User",
|
|
65
|
+
source: "client_chat",
|
|
66
|
+
channelId: `${agentName}-hash-memory`,
|
|
67
|
+
type: ChannelType.DM,
|
|
68
|
+
messageServerId,
|
|
69
|
+
metadata: { ownership: { ownerId: entityId } },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { roomId, entityId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function scoreMemoryText(text: string, query: string): number {
|
|
76
|
+
const normalizedText = text.toLowerCase();
|
|
77
|
+
const normalizedQuery = query.toLowerCase();
|
|
78
|
+
if (!normalizedText || !normalizedQuery) return 0;
|
|
79
|
+
|
|
80
|
+
const terms = normalizedQuery
|
|
81
|
+
.split(/\s+/)
|
|
82
|
+
.map((term) => term.trim())
|
|
83
|
+
.filter((term) => term.length >= 2);
|
|
84
|
+
|
|
85
|
+
const containsWhole = normalizedText.includes(normalizedQuery) ? 1 : 0;
|
|
86
|
+
if (terms.length === 0) {
|
|
87
|
+
return containsWhole;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let termMatches = 0;
|
|
91
|
+
for (const term of terms) {
|
|
92
|
+
if (normalizedText.includes(term)) termMatches += 1;
|
|
93
|
+
}
|
|
94
|
+
return containsWhole + termMatches / terms.length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function searchMemoryNotes(
|
|
98
|
+
runtime: AgentRuntime,
|
|
99
|
+
roomId: UUID,
|
|
100
|
+
query: string,
|
|
101
|
+
limit: number,
|
|
102
|
+
): Promise<MemorySearchHit[]> {
|
|
103
|
+
const memories = await runtime.getMemories({
|
|
104
|
+
roomId,
|
|
105
|
+
tableName: "messages",
|
|
106
|
+
count: MEMORY_SEARCH_SCAN_LIMIT,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const hits: MemorySearchHit[] = [];
|
|
110
|
+
for (const memory of memories) {
|
|
111
|
+
const text = (
|
|
112
|
+
memory.content as { text?: string } | undefined
|
|
113
|
+
)?.text?.trim();
|
|
114
|
+
if (!text) continue;
|
|
115
|
+
const source = (memory.content as { source?: string } | undefined)?.source;
|
|
116
|
+
if (source !== HASH_MEMORY_SOURCE) continue;
|
|
117
|
+
const score = scoreMemoryText(text, query);
|
|
118
|
+
if (score <= 0) continue;
|
|
119
|
+
hits.push({
|
|
120
|
+
id: memory.id ?? crypto.randomUUID(),
|
|
121
|
+
text,
|
|
122
|
+
createdAt: memory.createdAt ?? 0,
|
|
123
|
+
score,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
hits.sort((a, b) => {
|
|
128
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
129
|
+
return b.createdAt - a.createdAt;
|
|
130
|
+
});
|
|
131
|
+
return hits.slice(0, limit);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function searchKnowledge(
|
|
135
|
+
runtime: AgentRuntime,
|
|
136
|
+
query: string,
|
|
137
|
+
limit: number,
|
|
138
|
+
): Promise<KnowledgeSearchHit[]> {
|
|
139
|
+
const knowledge = await getKnowledgeService(runtime);
|
|
140
|
+
const knowledgeService = knowledge.service;
|
|
141
|
+
if (!knowledgeService || !runtime.agentId) return [];
|
|
142
|
+
|
|
143
|
+
const agentId = runtime.agentId as UUID;
|
|
144
|
+
const searchMessage: Memory = {
|
|
145
|
+
id: crypto.randomUUID() as UUID,
|
|
146
|
+
entityId: agentId,
|
|
147
|
+
agentId,
|
|
148
|
+
roomId: agentId,
|
|
149
|
+
content: { text: query },
|
|
150
|
+
createdAt: Date.now(),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const matches = await knowledgeService.getKnowledge(searchMessage, {
|
|
154
|
+
roomId: agentId,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return matches
|
|
158
|
+
.filter(
|
|
159
|
+
(match) => (match.similarity ?? 0) >= QUICK_CONTEXT_KNOWLEDGE_THRESHOLD,
|
|
160
|
+
)
|
|
161
|
+
.slice(0, limit)
|
|
162
|
+
.map((match) => {
|
|
163
|
+
const metadata = match.metadata as Record<string, unknown> | undefined;
|
|
164
|
+
return {
|
|
165
|
+
id: match.id,
|
|
166
|
+
text: match.content?.text ?? "",
|
|
167
|
+
similarity: match.similarity ?? 0,
|
|
168
|
+
documentId:
|
|
169
|
+
typeof metadata?.documentId === "string"
|
|
170
|
+
? metadata.documentId
|
|
171
|
+
: undefined,
|
|
172
|
+
documentTitle:
|
|
173
|
+
typeof metadata?.filename === "string"
|
|
174
|
+
? metadata.filename
|
|
175
|
+
: typeof metadata?.title === "string"
|
|
176
|
+
? metadata.title
|
|
177
|
+
: undefined,
|
|
178
|
+
position:
|
|
179
|
+
typeof metadata?.position === "number"
|
|
180
|
+
? metadata.position
|
|
181
|
+
: undefined,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildQuickContextPrompt(params: {
|
|
187
|
+
query: string;
|
|
188
|
+
memories: MemorySearchHit[];
|
|
189
|
+
knowledge: KnowledgeSearchHit[];
|
|
190
|
+
}): string {
|
|
191
|
+
const { query, memories, knowledge } = params;
|
|
192
|
+
const memorySection =
|
|
193
|
+
memories.length > 0
|
|
194
|
+
? memories
|
|
195
|
+
.map((item, index) => `- [M${index + 1}] ${item.text}`)
|
|
196
|
+
.join("\n")
|
|
197
|
+
: "- none";
|
|
198
|
+
const knowledgeSection =
|
|
199
|
+
knowledge.length > 0
|
|
200
|
+
? knowledge
|
|
201
|
+
.map((item, index) => `- [K${index + 1}] ${item.text}`)
|
|
202
|
+
.join("\n")
|
|
203
|
+
: "- none";
|
|
204
|
+
|
|
205
|
+
return [
|
|
206
|
+
"You are a concise context assistant.",
|
|
207
|
+
"Answer only from the provided context. If context is insufficient, say so explicitly.",
|
|
208
|
+
"Keep the answer under 120 words.",
|
|
209
|
+
"",
|
|
210
|
+
`Query: ${query}`,
|
|
211
|
+
"",
|
|
212
|
+
"Saved memory notes:",
|
|
213
|
+
memorySection,
|
|
214
|
+
"",
|
|
215
|
+
"Knowledge snippets:",
|
|
216
|
+
knowledgeSection,
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function handleMemoryRoutes(
|
|
221
|
+
ctx: MemoryRouteContext,
|
|
222
|
+
): Promise<boolean> {
|
|
223
|
+
const {
|
|
224
|
+
req,
|
|
225
|
+
res,
|
|
226
|
+
method,
|
|
227
|
+
pathname,
|
|
228
|
+
url,
|
|
229
|
+
runtime,
|
|
230
|
+
agentName,
|
|
231
|
+
json,
|
|
232
|
+
error,
|
|
233
|
+
readJsonBody,
|
|
234
|
+
} = ctx;
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
!pathname.startsWith("/api/memory") &&
|
|
238
|
+
pathname !== "/api/context/quick"
|
|
239
|
+
) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!runtime) {
|
|
244
|
+
error(res, "Agent runtime is not available", 503);
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const resolvedAgentName = resolveAgentName(runtime, agentName);
|
|
249
|
+
const { roomId, entityId } = await ensureMemoryConnection(
|
|
250
|
+
runtime,
|
|
251
|
+
resolvedAgentName,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
if (method === "POST" && pathname === "/api/memory/remember") {
|
|
255
|
+
const body = await readJsonBody<{ text?: string }>(req, res);
|
|
256
|
+
if (!body) return true;
|
|
257
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
258
|
+
if (!text) {
|
|
259
|
+
error(res, "text is required", 400);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
const createdAt = Date.now();
|
|
263
|
+
const message = createMessageMemory({
|
|
264
|
+
id: crypto.randomUUID() as UUID,
|
|
265
|
+
entityId,
|
|
266
|
+
roomId,
|
|
267
|
+
content: {
|
|
268
|
+
text,
|
|
269
|
+
source: HASH_MEMORY_SOURCE,
|
|
270
|
+
channelType: ChannelType.DM,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
await runtime.createMemory(message, "messages");
|
|
274
|
+
json(res, {
|
|
275
|
+
ok: true,
|
|
276
|
+
id: message.id,
|
|
277
|
+
text,
|
|
278
|
+
createdAt,
|
|
279
|
+
});
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (method === "GET" && pathname === "/api/memory/search") {
|
|
284
|
+
const query = url.searchParams.get("q")?.trim() ?? "";
|
|
285
|
+
if (!query) {
|
|
286
|
+
error(res, "Search query (q) is required", 400);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
const requestedLimit = parsePositiveInteger(
|
|
290
|
+
url.searchParams.get("limit"),
|
|
291
|
+
MEMORY_SEARCH_DEFAULT_LIMIT,
|
|
292
|
+
);
|
|
293
|
+
const limit = Math.min(
|
|
294
|
+
Math.max(requestedLimit, 1),
|
|
295
|
+
MEMORY_SEARCH_MAX_LIMIT,
|
|
296
|
+
);
|
|
297
|
+
const results = await searchMemoryNotes(runtime, roomId, query, limit);
|
|
298
|
+
json(res, {
|
|
299
|
+
query,
|
|
300
|
+
results,
|
|
301
|
+
count: results.length,
|
|
302
|
+
limit,
|
|
303
|
+
});
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (method === "GET" && pathname === "/api/context/quick") {
|
|
308
|
+
const query = url.searchParams.get("q")?.trim() ?? "";
|
|
309
|
+
if (!query) {
|
|
310
|
+
error(res, "Search query (q) is required", 400);
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
const requestedLimit = parsePositiveInteger(
|
|
314
|
+
url.searchParams.get("limit"),
|
|
315
|
+
QUICK_CONTEXT_DEFAULT_LIMIT,
|
|
316
|
+
);
|
|
317
|
+
const limit = Math.min(
|
|
318
|
+
Math.max(requestedLimit, 1),
|
|
319
|
+
QUICK_CONTEXT_MAX_LIMIT,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const [memories, knowledge] = await Promise.all([
|
|
323
|
+
searchMemoryNotes(runtime, roomId, query, limit),
|
|
324
|
+
searchKnowledge(runtime, query, limit),
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
const prompt = buildQuickContextPrompt({ query, memories, knowledge });
|
|
328
|
+
let answer = "I couldn't generate a quick answer right now.";
|
|
329
|
+
try {
|
|
330
|
+
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
|
|
331
|
+
const text = typeof response === "string" ? response : String(response);
|
|
332
|
+
if (text.trim()) {
|
|
333
|
+
answer = text.trim();
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
// Keep fallback answer.
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
json(res, {
|
|
340
|
+
query,
|
|
341
|
+
answer,
|
|
342
|
+
memories,
|
|
343
|
+
knowledge,
|
|
344
|
+
});
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
349
|
+
}
|