qlogicagent 0.2.1 → 0.3.0

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 (229) hide show
  1. package/dist/agent.js +1 -0
  2. package/dist/cli.js +9 -0
  3. package/dist/contracts.js +1 -0
  4. package/dist/index.js +5 -15
  5. package/dist/orchestration.js +118 -0
  6. package/package.json +56 -42
  7. package/dist/agent/agent.js +0 -113
  8. package/dist/agent/tool-loop.js +0 -575
  9. package/dist/agent/types.js +0 -14
  10. package/dist/cli/main.js +0 -23
  11. package/dist/cli/stdio-server.js +0 -463
  12. package/dist/config/config.js +0 -21
  13. package/dist/contracts/hooks.js +0 -7
  14. package/dist/contracts/index.js +0 -10
  15. package/dist/contracts/planner.js +0 -2
  16. package/dist/contracts/skill-candidate.js +0 -195
  17. package/dist/contracts/todo.js +0 -9
  18. package/dist/llm/builtin-providers.js +0 -531
  19. package/dist/llm/index.js +0 -14
  20. package/dist/llm/llm-client.js +0 -67
  21. package/dist/llm/model-catalog.js +0 -191
  22. package/dist/llm/provider-def.js +0 -12
  23. package/dist/llm/provider-registry.js +0 -147
  24. package/dist/llm/transport.js +0 -27
  25. package/dist/llm/transports/anthropic-messages.js +0 -293
  26. package/dist/llm/transports/openai-chat.js +0 -165
  27. package/dist/orchestration/agent-registry.js +0 -116
  28. package/dist/orchestration/approval-aware-tool-plan.js +0 -87
  29. package/dist/orchestration/context-compression.js +0 -583
  30. package/dist/orchestration/conversation-repair.js +0 -429
  31. package/dist/orchestration/curator-scheduler.js +0 -135
  32. package/dist/orchestration/embedded-failover-policy.js +0 -168
  33. package/dist/orchestration/error-classification.js +0 -77
  34. package/dist/orchestration/failover-classification.js +0 -381
  35. package/dist/orchestration/failover-error.js +0 -198
  36. package/dist/orchestration/fork-subagent.js +0 -98
  37. package/dist/orchestration/index.js +0 -267
  38. package/dist/orchestration/memory-flush-policy.js +0 -85
  39. package/dist/orchestration/memory-provider.js +0 -2
  40. package/dist/orchestration/parallel-tool-calls.js +0 -59
  41. package/dist/orchestration/prompt-cache-strategy.js +0 -228
  42. package/dist/orchestration/reactive-compact.js +0 -78
  43. package/dist/orchestration/retry-loop.js +0 -24
  44. package/dist/orchestration/skill-candidate.js +0 -141
  45. package/dist/orchestration/skill-consolidation.js +0 -220
  46. package/dist/orchestration/skill-improvement.js +0 -66
  47. package/dist/orchestration/skill-similarity.js +0 -131
  48. package/dist/orchestration/streaming-tool-executor.js +0 -96
  49. package/dist/orchestration/team-orchestration.js +0 -369
  50. package/dist/orchestration/team-tool-loop-wiring.js +0 -147
  51. package/dist/orchestration/tool-choice-policy.js +0 -164
  52. package/dist/orchestration/tool-loop-state.js +0 -133
  53. package/dist/orchestration/tool-schema.js +0 -297
  54. package/dist/orchestration/transcript-repair.js +0 -426
  55. package/dist/orchestration/turn-loop-guard.js +0 -92
  56. package/dist/orchestration/web-browser-policy.js +0 -39
  57. package/dist/runtime/context-compression.js +0 -274
  58. package/dist/runtime/hook-registry.js +0 -53
  59. package/dist/runtime/memory-hooks.js +0 -65
  60. package/dist/runtime/tool-eligibility.js +0 -111
  61. package/dist/skills/index.js +0 -82
  62. package/dist/skills/memory-extractor.js +0 -173
  63. package/dist/skills/memory-query-tool.js +0 -127
  64. package/dist/skills/memory-store.js +0 -228
  65. package/dist/skills/memory-tool.js +0 -192
  66. package/dist/skills/portable-tool.js +0 -14
  67. package/dist/skills/qmemory-adapter.js +0 -165
  68. package/dist/skills/skill-frontmatter.js +0 -344
  69. package/dist/skills/skill-guard.js +0 -229
  70. package/dist/skills/skill-loader.js +0 -303
  71. package/dist/skills/skill-source.js +0 -126
  72. package/dist/skills/skill-types.js +0 -6
  73. package/dist/skills/think-tool.js +0 -59
  74. package/dist/skills/todo-tool.js +0 -114
  75. package/dist/skills/tools/agent-tool.js +0 -142
  76. package/dist/skills/tools/apply-patch-tool.js +0 -184
  77. package/dist/skills/tools/ask-user-tool.js +0 -121
  78. package/dist/skills/tools/brief-tool.js +0 -95
  79. package/dist/skills/tools/browser-tool.js +0 -155
  80. package/dist/skills/tools/checkpoint-tool.js +0 -102
  81. package/dist/skills/tools/config-tool.js +0 -143
  82. package/dist/skills/tools/cron-tool.js +0 -175
  83. package/dist/skills/tools/edit-tool.js +0 -70
  84. package/dist/skills/tools/exec-tool.js +0 -133
  85. package/dist/skills/tools/image-generate-tool.js +0 -67
  86. package/dist/skills/tools/instructions-tool.js +0 -187
  87. package/dist/skills/tools/lsp-tool.js +0 -227
  88. package/dist/skills/tools/mcp-client-types.js +0 -53
  89. package/dist/skills/tools/mcp-tool.js +0 -503
  90. package/dist/skills/tools/memory-tool.js +0 -88
  91. package/dist/skills/tools/monitor-tool.js +0 -131
  92. package/dist/skills/tools/music-generate-tool.js +0 -62
  93. package/dist/skills/tools/notify-tool.js +0 -62
  94. package/dist/skills/tools/patch-tool.js +0 -505
  95. package/dist/skills/tools/pdf-tool.js +0 -88
  96. package/dist/skills/tools/plan-mode-tool.js +0 -122
  97. package/dist/skills/tools/read-tool.js +0 -84
  98. package/dist/skills/tools/repl-tool.js +0 -69
  99. package/dist/skills/tools/search-tool.js +0 -225
  100. package/dist/skills/tools/send-message-tool.js +0 -76
  101. package/dist/skills/tools/skill-list-tool.js +0 -54
  102. package/dist/skills/tools/skill-manage-tool.js +0 -153
  103. package/dist/skills/tools/skill-view-tool.js +0 -72
  104. package/dist/skills/tools/sleep-tool.js +0 -81
  105. package/dist/skills/tools/structured-output-tool.js +0 -176
  106. package/dist/skills/tools/task-tool.js +0 -161
  107. package/dist/skills/tools/team-tool.js +0 -105
  108. package/dist/skills/tools/tool-search-tool.js +0 -110
  109. package/dist/skills/tools/tts-tool.js +0 -45
  110. package/dist/skills/tools/video-edit-tool.js +0 -74
  111. package/dist/skills/tools/video-generate-tool.js +0 -66
  112. package/dist/skills/tools/video-merge-tool.js +0 -92
  113. package/dist/skills/tools/video-upscale-tool.js +0 -52
  114. package/dist/skills/tools/web-fetch-tool.js +0 -92
  115. package/dist/skills/tools/web-search-tool.js +0 -86
  116. package/dist/skills/tools/worktree-tool.js +0 -147
  117. package/dist/skills/tools/write-tool.js +0 -81
  118. /package/dist/{agent → types/agent}/agent.d.ts +0 -0
  119. /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
  120. /package/dist/{agent → types/agent}/types.d.ts +0 -0
  121. /package/dist/{cli → types/cli}/main.d.ts +0 -0
  122. /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
  123. /package/dist/{config → types/config}/config.d.ts +0 -0
  124. /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
  125. /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
  126. /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
  127. /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
  128. /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
  129. /package/dist/{index.d.ts → types/index.d.ts} +0 -0
  130. /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
  131. /package/dist/{llm → types/llm}/index.d.ts +0 -0
  132. /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
  133. /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
  134. /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
  135. /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
  136. /package/dist/{llm → types/llm}/transport.d.ts +0 -0
  137. /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
  138. /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
  139. /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
  140. /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
  141. /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
  142. /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
  143. /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
  144. /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
  145. /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
  146. /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
  147. /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
  148. /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
  149. /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
  150. /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
  151. /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
  152. /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
  153. /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
  154. /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
  155. /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
  156. /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
  157. /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
  158. /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
  159. /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
  160. /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
  161. /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
  162. /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
  163. /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
  164. /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
  165. /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
  166. /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
  167. /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
  168. /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
  169. /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
  170. /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
  171. /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
  172. /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
  173. /package/dist/{skills → types/skills}/index.d.ts +0 -0
  174. /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
  175. /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
  176. /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
  177. /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
  178. /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
  179. /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
  180. /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
  181. /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
  182. /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
  183. /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
  184. /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
  185. /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
  186. /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
  187. /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
  188. /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
  189. /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
  190. /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
  191. /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
  192. /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
  193. /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
  194. /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
  195. /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
  196. /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
  197. /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
  198. /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
  199. /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
  200. /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
  201. /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
  202. /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
  203. /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
  204. /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
  205. /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
  206. /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
  207. /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
  208. /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
  209. /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
  210. /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
  211. /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
  212. /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
  213. /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
  214. /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
  215. /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
  216. /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
  217. /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
  218. /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
  219. /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
  220. /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
  221. /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
  222. /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
  223. /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
  224. /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
  225. /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
  226. /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
  227. /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
  228. /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
  229. /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
@@ -1,173 +0,0 @@
1
- // ============================================================
2
- // Memory Extractor — LLM-based fact extraction from conversation.
3
- //
4
- // Runs server-side via an OpenAI-compatible LLM endpoint (e.g.
5
- // admin/new-api). Ports qmemory's extraction prompt into a
6
- // reusable TypeScript module so extraction happens where the
7
- // LLM key lives, NOT on the local qmemory instance.
8
- // ============================================================
9
- // ── Extraction Tool Schema (matches qmemory extractor) ──────
10
- const EXTRACTION_TOOL = {
11
- type: "function",
12
- function: {
13
- name: "save_extraction",
14
- description: "Save all extracted memories and profile updates from the conversation.",
15
- parameters: {
16
- type: "object",
17
- properties: {
18
- memories: {
19
- type: "array",
20
- description: "Atomic facts extracted from both user and assistant. One per distinct fact.",
21
- items: {
22
- type: "object",
23
- properties: {
24
- text: { type: "string", description: "Self-contained fact (one sentence, no pronouns)" },
25
- category: { type: "string", enum: ["fact", "preference", "event", "plan", "opinion", "advice", "discussion"] },
26
- importance: { type: "number", description: "0-1 scale. Named entities>=0.5, facts with numbers>=0.4, preferences>=0.6, identity>=0.8" },
27
- speaker: { type: "string", enum: ["user", "assistant", "both"] },
28
- event_date: { type: "string", description: "ISO YYYY-MM-DD or null" },
29
- tags: { type: "array", items: { type: "string" }, maxItems: 3 },
30
- },
31
- required: ["text", "category", "importance", "speaker"],
32
- },
33
- },
34
- profile_updates: {
35
- type: "array",
36
- description: "User traits/preferences to track.",
37
- items: {
38
- type: "object",
39
- properties: {
40
- category: { type: "string", enum: ["preference", "fact", "habit", "style"] },
41
- key: { type: "string", description: "Short ID (e.g. job, location)" },
42
- value: { type: "string" },
43
- confidence: { type: "number" },
44
- },
45
- required: ["category", "key", "value"],
46
- },
47
- },
48
- },
49
- required: ["memories"],
50
- },
51
- },
52
- };
53
- // ── System Prompt (ported from qmemory extractor.py) ────────
54
- function buildSystemPrompt(today, isChinese) {
55
- const base = `You are a memory extraction engine. Today's date: ${today}.
56
-
57
- Extract from the conversation below by calling the save_extraction tool with ALL results at once.
58
-
59
- Rules:
60
- - **LANGUAGE**: Write in the SAME language as the conversation. NEVER translate.
61
- - Extract ALL meaningful facts from BOTH [User] and [Assistant] lines. Skip greetings/filler.
62
- - **BE THOROUGH**: Extract EVERY named person+role, activity+details, financial/numeric detail, temporal event, relationship, item owned, change over time, and assistant's recommendations.
63
- - **ANTI-SUMMARIZATION**: Keep exact numbers, locations, dates, names, metrics.
64
- - For event_date: convert relative dates using today (${today}).
65
- - Set speaker="assistant" for assistant-originated facts.
66
- - **IMPORTANCE SCORING**:
67
- 0.9-1.0: Core identity (name, DOB, nationality)
68
- 0.7-0.9: Key relationships, major life events, career facts
69
- 0.5-0.7: Specific facts with names/numbers/dates, preferences, hobbies
70
- 0.3-0.5: Casual mentions, assistant recommendations, general opinions
71
- NEVER assign importance < 0.2 to any memory containing proper nouns, numbers, dates, or specific entities.`;
72
- if (isChinese) {
73
- return base + `\n- Extract EVERY number/price/measurement as separate memories with exact figures.\n- Minimum density: ~2-3 memories per 200 tokens of conversation.`;
74
- }
75
- return base;
76
- }
77
- function isChinese(text) {
78
- let cn = 0;
79
- for (const c of text) {
80
- if (c >= "\u4e00" && c <= "\u9fff")
81
- cn++;
82
- }
83
- return cn / (text.length || 1) > 0.15;
84
- }
85
- // ── Parser ──────────────────────────────────────────────────
86
- function parseToolCallResult(argsJson) {
87
- let parsed;
88
- try {
89
- parsed = JSON.parse(argsJson);
90
- }
91
- catch {
92
- return { memories: [], profileUpdates: [] };
93
- }
94
- const rawMemories = Array.isArray(parsed.memories) ? parsed.memories : [];
95
- const rawProfiles = Array.isArray(parsed.profile_updates) ? parsed.profile_updates : [];
96
- const memories = rawMemories
97
- .filter((m) => !!m && typeof m === "object")
98
- .filter((m) => typeof m.text === "string" && m.text.trim())
99
- .map((m) => ({
100
- text: String(m.text).trim(),
101
- category: typeof m.category === "string" ? m.category : "fact",
102
- importance: typeof m.importance === "number" ? Math.max(0, Math.min(1, m.importance)) : 0.5,
103
- speaker: typeof m.speaker === "string" ? m.speaker : "user",
104
- ...(typeof m.event_date === "string" && m.event_date ? { event_date: m.event_date } : {}),
105
- ...(Array.isArray(m.tags) ? { tags: m.tags.filter((t) => typeof t === "string") } : {}),
106
- }));
107
- const profileUpdates = rawProfiles
108
- .filter((p) => !!p && typeof p === "object")
109
- .filter((p) => typeof p.key === "string" && typeof p.value === "string")
110
- .map((p) => ({
111
- category: typeof p.category === "string" ? p.category : "fact",
112
- key: String(p.key).trim(),
113
- value: String(p.value).trim(),
114
- confidence: typeof p.confidence === "number" ? Math.max(0, Math.min(1, p.confidence)) : 0.5,
115
- }));
116
- return { memories, profileUpdates };
117
- }
118
- // ── Factory ─────────────────────────────────────────────────
119
- const DEFAULT_MODEL = "deepseek-chat";
120
- const MIN_TEXT_LENGTH = 20;
121
- /**
122
- * Create a memory extractor backed by an OpenAI-compatible LLM endpoint.
123
- *
124
- * The `complete` function is injected by the consumer so that
125
- * skills layer stays transport-agnostic (no admin/new-api dependency).
126
- */
127
- export function createMemoryExtractor(complete, options) {
128
- const model = options?.model ?? DEFAULT_MODEL;
129
- return {
130
- /**
131
- * Extract structured memories from a conversation fragment.
132
- * Returns empty result if text is too short or LLM call fails.
133
- */
134
- async extract(conversationText) {
135
- const trimmed = conversationText.trim();
136
- if (trimmed.length < MIN_TEXT_LENGTH) {
137
- return { memories: [], profileUpdates: [] };
138
- }
139
- const today = new Date().toISOString().slice(0, 10);
140
- const systemPrompt = buildSystemPrompt(today, isChinese(trimmed));
141
- try {
142
- const response = await complete({
143
- model,
144
- messages: [
145
- { role: "system", content: systemPrompt },
146
- { role: "user", content: `Conversation:\n${trimmed}` },
147
- ],
148
- tools: [EXTRACTION_TOOL],
149
- tool_choice: { type: "function", function: { name: "save_extraction" } },
150
- temperature: 0.1,
151
- max_tokens: 4000,
152
- });
153
- const toolCalls = response.choices?.[0]?.message?.tool_calls;
154
- if (!toolCalls?.length) {
155
- return { memories: [], profileUpdates: [] };
156
- }
157
- // Merge results from all tool calls (usually just one)
158
- const merged = { memories: [], profileUpdates: [] };
159
- for (const tc of toolCalls) {
160
- if (tc.function?.name === "save_extraction" && tc.function.arguments) {
161
- const partial = parseToolCallResult(tc.function.arguments);
162
- merged.memories.push(...partial.memories);
163
- merged.profileUpdates.push(...partial.profileUpdates);
164
- }
165
- }
166
- return merged;
167
- }
168
- catch {
169
- return { memories: [], profileUpdates: [] };
170
- }
171
- },
172
- };
173
- }
@@ -1,127 +0,0 @@
1
- // ============================================================
2
- // Memory Query Tool — explicit agent-initiated memory recall.
3
- //
4
- // Allows the LLM to actively query recalled memories and user
5
- // profile when it determines that the current context is
6
- // insufficient for a good response.
7
- // ============================================================
8
- export const MEMORY_QUERY_TOOL_NAME = "memory_query";
9
- // ── Schema ──────────────────────────────────────────────────
10
- export const MEMORY_QUERY_TOOL_SCHEMA = {
11
- type: "object",
12
- properties: {
13
- query: {
14
- type: "string",
15
- description: [
16
- "The memory query — a natural language description of what you want to recall.",
17
- "Examples:",
18
- "• '用户的编程语言偏好' → searches tech preferences",
19
- "• '用户之前提到的项目' → searches project history",
20
- "• '上次画图的风格' → searches media generation history",
21
- "• 'user's name and location' → searches personal facts",
22
- "Be specific: '用户喜欢什么图片风格' is better than '用户偏好'.",
23
- ].join("\n"),
24
- },
25
- category: {
26
- type: "string",
27
- description: [
28
- "Optional: narrow the search to a specific category.",
29
- "• 'profile' — user preferences, expertise, communication style",
30
- "• 'facts' — recalled long-term memories",
31
- "• 'media' — media generation preferences (image style, music genre, etc.)",
32
- "• 'projects' — project context and tech stack",
33
- "If omitted, searches across all categories.",
34
- ].join("\n"),
35
- },
36
- },
37
- required: ["query"],
38
- };
39
- // ── Tool factory ────────────────────────────────────────────
40
- export function createMemoryQueryTool(deps) {
41
- return {
42
- name: MEMORY_QUERY_TOOL_NAME,
43
- label: "Memory Query",
44
- description: [
45
- "Query your memory about this user to recall facts, preferences, or history.",
46
- "Use this when you need information that might have been shared in previous conversations.",
47
- "",
48
- "Call this tool when:",
49
- "- You want to personalize a response based on user preferences",
50
- "- You recall the user mentioned something relevant but it's not in the current context",
51
- "- You need to check media preferences before generating images/music/video",
52
- "- You want to reference the user's project context or tech stack",
53
- "",
54
- "This tool searches recalled memories and the user profile.",
55
- "It does NOT search the current conversation — that's already in your context.",
56
- "Results may be empty if no relevant memories exist.",
57
- ].join("\n"),
58
- parameters: MEMORY_QUERY_TOOL_SCHEMA,
59
- execute: async (_toolCallId, params) => {
60
- const query = params.query?.trim();
61
- if (!query) {
62
- return {
63
- content: [{ type: "text", text: "query parameter is required." }],
64
- details: { type: "memory_query", found: false, errorCode: "empty_query" },
65
- };
66
- }
67
- const category = params.category?.trim() || undefined;
68
- const userId = deps?.userId ?? "user";
69
- const sections = [];
70
- // 1. Memory recall search
71
- if (!category || category === "facts" || category === "projects") {
72
- try {
73
- const facts = deps?.queryGraph?.(query, userId) ?? [];
74
- if (facts.length > 0) {
75
- sections.push("## Recalled Memories\n" + facts.map((f) => `• ${f}`).join("\n"));
76
- }
77
- }
78
- catch { /* degrade gracefully */ }
79
- }
80
- // 2. User Profile summary
81
- if (!category || category === "profile" || category === "projects") {
82
- try {
83
- const profileText = deps?.getProfileSummary?.(userId) ?? null;
84
- if (profileText) {
85
- sections.push("## User Profile\n" + profileText);
86
- }
87
- }
88
- catch { /* degrade gracefully */ }
89
- }
90
- // 3. Media Preferences
91
- if (!category || category === "media") {
92
- try {
93
- const mediaPrefs = deps?.getMediaPreferences?.(userId) ?? null;
94
- if (mediaPrefs) {
95
- const lines = [];
96
- if (mediaPrefs.imageStyle)
97
- lines.push(`• Image style: ${mediaPrefs.imageStyle}`);
98
- if (mediaPrefs.videoStyle)
99
- lines.push(`• Video style: ${mediaPrefs.videoStyle}`);
100
- if (mediaPrefs.musicGenre)
101
- lines.push(`• Music genre: ${mediaPrefs.musicGenre}`);
102
- if (mediaPrefs.musicMood)
103
- lines.push(`• Music mood: ${mediaPrefs.musicMood}`);
104
- if (mediaPrefs.primaryPurpose)
105
- lines.push(`• Primary purpose: ${mediaPrefs.primaryPurpose}`);
106
- if (mediaPrefs.colorPreference)
107
- lines.push(`• Color preference: ${mediaPrefs.colorPreference}`);
108
- if (lines.length > 0) {
109
- sections.push("## Media Preferences\n" + lines.join("\n"));
110
- }
111
- }
112
- }
113
- catch { /* degrade gracefully */ }
114
- }
115
- if (sections.length === 0) {
116
- return {
117
- content: [{ type: "text", text: "No relevant memories found for this query. The user may not have shared this information yet." }],
118
- details: { type: "memory_query", found: false, query },
119
- };
120
- }
121
- return {
122
- content: [{ type: "text", text: sections.join("\n\n") }],
123
- details: { type: "memory_query", found: true, query, category: category ?? "all", sectionCount: sections.length },
124
- };
125
- },
126
- };
127
- }
@@ -1,228 +0,0 @@
1
- // ============================================================
2
- // Memory Store — character-bounded local memory for agent notes.
3
- //
4
- // Inspired by Hermes' MemoryStore design:
5
- // - Two targets: "memory" (agent notes) + "user" (user profile)
6
- // - § delimiter between entries
7
- // - Frozen snapshot for system prompt (KV cache stability)
8
- // - Exact duplicate rejection
9
- // - Content safety scanning
10
- // - Pure in-memory; persistence is the consumer's responsibility.
11
- // ============================================================
12
- import { isMemoryContentSafe } from "./memory-tool.js";
13
- // ── Constants ───────────────────────────────────────────────
14
- export const MEMORY_ENTRY_DELIMITER = "\n§\n";
15
- export const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
16
- export const DEFAULT_USER_CHAR_LIMIT = 1375;
17
- // ── MemoryStore class ───────────────────────────────────────
18
- export class MemoryStore {
19
- memoryEntries = [];
20
- userEntries = [];
21
- frozenSnapshot = { memory: "", user: "" };
22
- snapshotFrozen = false;
23
- memoryCharLimit;
24
- userCharLimit;
25
- constructor(options) {
26
- this.memoryCharLimit = options?.memoryCharLimit ?? DEFAULT_MEMORY_CHAR_LIMIT;
27
- this.userCharLimit = options?.userCharLimit ?? DEFAULT_USER_CHAR_LIMIT;
28
- }
29
- // ── Load / Serialize ────────────────────────────────────
30
- /**
31
- * Load from serialized form (e.g. from PG or filesystem).
32
- * Deduplicates entries on load.
33
- */
34
- loadFromSerialized(data) {
35
- this.memoryEntries = deduplicateEntries(parseEntries(data.memory ?? ""));
36
- this.userEntries = deduplicateEntries(parseEntries(data.user ?? ""));
37
- }
38
- /**
39
- * Serialize current live state for persistence.
40
- */
41
- serialize() {
42
- return {
43
- memory: this.memoryEntries.join(MEMORY_ENTRY_DELIMITER),
44
- user: this.userEntries.join(MEMORY_ENTRY_DELIMITER),
45
- };
46
- }
47
- // ── Snapshot (frozen for system prompt) ─────────────────
48
- /**
49
- * Freeze current entries for system prompt injection.
50
- * Call once at session start. After freezing, mutations update
51
- * live state but NOT the system prompt block.
52
- */
53
- freezeSnapshot() {
54
- this.frozenSnapshot = {
55
- memory: this.renderBlock("memory", this.memoryEntries),
56
- user: this.renderBlock("user", this.userEntries),
57
- };
58
- this.snapshotFrozen = true;
59
- }
60
- /**
61
- * Get frozen system prompt block for the given target.
62
- * Returns empty string if no entries or not yet frozen.
63
- */
64
- getSystemPromptBlock(target) {
65
- if (!this.snapshotFrozen) {
66
- // Auto-freeze on first access
67
- this.freezeSnapshot();
68
- }
69
- return this.frozenSnapshot[target];
70
- }
71
- // ── Mutations ───────────────────────────────────────────
72
- add(target, content) {
73
- const trimmed = content.trim();
74
- if (!trimmed) {
75
- return this.errorResult(target, "Content cannot be empty.", "empty_content");
76
- }
77
- if (!isMemoryContentSafe(trimmed)) {
78
- return this.errorResult(target, "Content rejected: potential injection detected.", "unsafe_content");
79
- }
80
- const entries = this.entriesFor(target);
81
- // Exact duplicate check
82
- if (entries.includes(trimmed)) {
83
- return this.successResult(target, "Entry already exists (no change).");
84
- }
85
- // Character budget check
86
- const newEntries = [...entries, trimmed];
87
- const newTotal = newEntries.join(MEMORY_ENTRY_DELIMITER).length;
88
- if (newTotal > this.charLimitFor(target)) {
89
- const usage = this.formatUsage(target);
90
- return this.errorResult(target, `Memory full (${usage}). Remove old entries first.`, "over_limit");
91
- }
92
- entries.push(trimmed);
93
- return this.successResult(target, "Entry added.");
94
- }
95
- replace(target, oldText, newContent) {
96
- const searchText = oldText.trim();
97
- const replacement = newContent.trim();
98
- if (!searchText) {
99
- return this.errorResult(target, "old_text cannot be empty.", "empty_old_text");
100
- }
101
- if (!replacement) {
102
- return this.errorResult(target, "content cannot be empty.", "empty_content");
103
- }
104
- if (!isMemoryContentSafe(replacement)) {
105
- return this.errorResult(target, "Content rejected: potential injection detected.", "unsafe_content");
106
- }
107
- const entries = this.entriesFor(target);
108
- const matchIndices = entries
109
- .map((entry, i) => entry.includes(searchText) ? i : -1)
110
- .filter((i) => i >= 0);
111
- if (matchIndices.length === 0) {
112
- return this.errorResult(target, `No entry contains: "${clipForMessage(searchText)}"`, "not_found");
113
- }
114
- if (matchIndices.length > 1) {
115
- // Check if all matches are identical (harmless duplicate)
116
- const unique = new Set(matchIndices.map((i) => entries[i]));
117
- if (unique.size > 1) {
118
- return this.errorResult(target, `Multiple entries match: "${clipForMessage(searchText)}" (${matchIndices.length} matches). Be more specific.`, "ambiguous_match");
119
- }
120
- }
121
- // Replace first match
122
- const matchIndex = matchIndices[0];
123
- // Check char limit with replacement
124
- const projected = [...entries];
125
- projected[matchIndex] = replacement;
126
- const projectedTotal = projected.join(MEMORY_ENTRY_DELIMITER).length;
127
- if (projectedTotal > this.charLimitFor(target)) {
128
- const usage = this.formatUsage(target);
129
- return this.errorResult(target, `Replacement would exceed limit (${usage}).`, "over_limit");
130
- }
131
- entries[matchIndex] = replacement;
132
- return this.successResult(target, "Entry replaced.");
133
- }
134
- remove(target, oldText) {
135
- const searchText = oldText.trim();
136
- if (!searchText) {
137
- return this.errorResult(target, "old_text cannot be empty.", "empty_old_text");
138
- }
139
- const entries = this.entriesFor(target);
140
- const matchIndices = entries
141
- .map((entry, i) => entry.includes(searchText) ? i : -1)
142
- .filter((i) => i >= 0);
143
- if (matchIndices.length === 0) {
144
- return this.errorResult(target, `No entry contains: "${clipForMessage(searchText)}"`, "not_found");
145
- }
146
- // Remove all matches (safe: if they all contain the substring, remove them all)
147
- for (let i = matchIndices.length - 1; i >= 0; i--) {
148
- entries.splice(matchIndices[i], 1);
149
- }
150
- return this.successResult(target, matchIndices.length === 1 ? "Entry removed." : `${matchIndices.length} entries removed.`);
151
- }
152
- // ── Read-only accessors ─────────────────────────────────
153
- getEntries(target) {
154
- return this.entriesFor(target);
155
- }
156
- getUsage(target) {
157
- const entries = this.entriesFor(target);
158
- const used = entries.length > 0 ? entries.join(MEMORY_ENTRY_DELIMITER).length : 0;
159
- const limit = this.charLimitFor(target);
160
- return { used, limit, percent: limit > 0 ? Math.round((used / limit) * 100) : 0 };
161
- }
162
- isEmpty() {
163
- return this.memoryEntries.length === 0 && this.userEntries.length === 0;
164
- }
165
- // ── Private helpers ─────────────────────────────────────
166
- entriesFor(target) {
167
- return target === "user" ? this.userEntries : this.memoryEntries;
168
- }
169
- charLimitFor(target) {
170
- return target === "user" ? this.userCharLimit : this.memoryCharLimit;
171
- }
172
- formatUsage(target) {
173
- const { used, limit, percent } = this.getUsage(target);
174
- return `${percent}% — ${used}/${limit} chars`;
175
- }
176
- renderBlock(target, entries) {
177
- if (entries.length === 0)
178
- return "";
179
- const label = target === "user" ? "USER PROFILE" : "MEMORY";
180
- const usage = this.formatUsage(target);
181
- const divider = "══════════════════════════════════════════════════════════";
182
- const body = entries.join(MEMORY_ENTRY_DELIMITER);
183
- return `${divider}\n${label} (your personal notes) [${usage}]\n${divider}\n${body}`;
184
- }
185
- successResult(target, message) {
186
- const entries = this.entriesFor(target);
187
- return {
188
- ok: true,
189
- message,
190
- target,
191
- entries,
192
- entryCount: entries.length,
193
- usage: this.formatUsage(target),
194
- };
195
- }
196
- errorResult(target, message, errorCode) {
197
- const entries = this.entriesFor(target);
198
- return {
199
- ok: false,
200
- message,
201
- target,
202
- entries,
203
- entryCount: entries.length,
204
- usage: this.formatUsage(target),
205
- errorCode,
206
- };
207
- }
208
- }
209
- // ── Utilities ─────────────────────────────────────────────
210
- function parseEntries(raw) {
211
- if (!raw.trim())
212
- return [];
213
- return raw.split(MEMORY_ENTRY_DELIMITER).map((e) => e.trim()).filter(Boolean);
214
- }
215
- function deduplicateEntries(entries) {
216
- const seen = new Set();
217
- return entries.filter((entry) => {
218
- if (seen.has(entry))
219
- return false;
220
- seen.add(entry);
221
- return true;
222
- });
223
- }
224
- function clipForMessage(text, maxLength = 60) {
225
- if (text.length <= maxLength)
226
- return text;
227
- return `${text.slice(0, maxLength - 1).trimEnd()}…`;
228
- }