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.
- package/dist/agent.js +1 -0
- package/dist/cli.js +9 -0
- package/dist/contracts.js +1 -0
- package/dist/index.js +5 -15
- package/dist/orchestration.js +118 -0
- package/package.json +56 -42
- package/dist/agent/agent.js +0 -113
- package/dist/agent/tool-loop.js +0 -575
- package/dist/agent/types.js +0 -14
- package/dist/cli/main.js +0 -23
- package/dist/cli/stdio-server.js +0 -463
- package/dist/config/config.js +0 -21
- package/dist/contracts/hooks.js +0 -7
- package/dist/contracts/index.js +0 -10
- package/dist/contracts/planner.js +0 -2
- package/dist/contracts/skill-candidate.js +0 -195
- package/dist/contracts/todo.js +0 -9
- package/dist/llm/builtin-providers.js +0 -531
- package/dist/llm/index.js +0 -14
- package/dist/llm/llm-client.js +0 -67
- package/dist/llm/model-catalog.js +0 -191
- package/dist/llm/provider-def.js +0 -12
- package/dist/llm/provider-registry.js +0 -147
- package/dist/llm/transport.js +0 -27
- package/dist/llm/transports/anthropic-messages.js +0 -293
- package/dist/llm/transports/openai-chat.js +0 -165
- package/dist/orchestration/agent-registry.js +0 -116
- package/dist/orchestration/approval-aware-tool-plan.js +0 -87
- package/dist/orchestration/context-compression.js +0 -583
- package/dist/orchestration/conversation-repair.js +0 -429
- package/dist/orchestration/curator-scheduler.js +0 -135
- package/dist/orchestration/embedded-failover-policy.js +0 -168
- package/dist/orchestration/error-classification.js +0 -77
- package/dist/orchestration/failover-classification.js +0 -381
- package/dist/orchestration/failover-error.js +0 -198
- package/dist/orchestration/fork-subagent.js +0 -98
- package/dist/orchestration/index.js +0 -267
- package/dist/orchestration/memory-flush-policy.js +0 -85
- package/dist/orchestration/memory-provider.js +0 -2
- package/dist/orchestration/parallel-tool-calls.js +0 -59
- package/dist/orchestration/prompt-cache-strategy.js +0 -228
- package/dist/orchestration/reactive-compact.js +0 -78
- package/dist/orchestration/retry-loop.js +0 -24
- package/dist/orchestration/skill-candidate.js +0 -141
- package/dist/orchestration/skill-consolidation.js +0 -220
- package/dist/orchestration/skill-improvement.js +0 -66
- package/dist/orchestration/skill-similarity.js +0 -131
- package/dist/orchestration/streaming-tool-executor.js +0 -96
- package/dist/orchestration/team-orchestration.js +0 -369
- package/dist/orchestration/team-tool-loop-wiring.js +0 -147
- package/dist/orchestration/tool-choice-policy.js +0 -164
- package/dist/orchestration/tool-loop-state.js +0 -133
- package/dist/orchestration/tool-schema.js +0 -297
- package/dist/orchestration/transcript-repair.js +0 -426
- package/dist/orchestration/turn-loop-guard.js +0 -92
- package/dist/orchestration/web-browser-policy.js +0 -39
- package/dist/runtime/context-compression.js +0 -274
- package/dist/runtime/hook-registry.js +0 -53
- package/dist/runtime/memory-hooks.js +0 -65
- package/dist/runtime/tool-eligibility.js +0 -111
- package/dist/skills/index.js +0 -82
- package/dist/skills/memory-extractor.js +0 -173
- package/dist/skills/memory-query-tool.js +0 -127
- package/dist/skills/memory-store.js +0 -228
- package/dist/skills/memory-tool.js +0 -192
- package/dist/skills/portable-tool.js +0 -14
- package/dist/skills/qmemory-adapter.js +0 -165
- package/dist/skills/skill-frontmatter.js +0 -344
- package/dist/skills/skill-guard.js +0 -229
- package/dist/skills/skill-loader.js +0 -303
- package/dist/skills/skill-source.js +0 -126
- package/dist/skills/skill-types.js +0 -6
- package/dist/skills/think-tool.js +0 -59
- package/dist/skills/todo-tool.js +0 -114
- package/dist/skills/tools/agent-tool.js +0 -142
- package/dist/skills/tools/apply-patch-tool.js +0 -184
- package/dist/skills/tools/ask-user-tool.js +0 -121
- package/dist/skills/tools/brief-tool.js +0 -95
- package/dist/skills/tools/browser-tool.js +0 -155
- package/dist/skills/tools/checkpoint-tool.js +0 -102
- package/dist/skills/tools/config-tool.js +0 -143
- package/dist/skills/tools/cron-tool.js +0 -175
- package/dist/skills/tools/edit-tool.js +0 -70
- package/dist/skills/tools/exec-tool.js +0 -133
- package/dist/skills/tools/image-generate-tool.js +0 -67
- package/dist/skills/tools/instructions-tool.js +0 -187
- package/dist/skills/tools/lsp-tool.js +0 -227
- package/dist/skills/tools/mcp-client-types.js +0 -53
- package/dist/skills/tools/mcp-tool.js +0 -503
- package/dist/skills/tools/memory-tool.js +0 -88
- package/dist/skills/tools/monitor-tool.js +0 -131
- package/dist/skills/tools/music-generate-tool.js +0 -62
- package/dist/skills/tools/notify-tool.js +0 -62
- package/dist/skills/tools/patch-tool.js +0 -505
- package/dist/skills/tools/pdf-tool.js +0 -88
- package/dist/skills/tools/plan-mode-tool.js +0 -122
- package/dist/skills/tools/read-tool.js +0 -84
- package/dist/skills/tools/repl-tool.js +0 -69
- package/dist/skills/tools/search-tool.js +0 -225
- package/dist/skills/tools/send-message-tool.js +0 -76
- package/dist/skills/tools/skill-list-tool.js +0 -54
- package/dist/skills/tools/skill-manage-tool.js +0 -153
- package/dist/skills/tools/skill-view-tool.js +0 -72
- package/dist/skills/tools/sleep-tool.js +0 -81
- package/dist/skills/tools/structured-output-tool.js +0 -176
- package/dist/skills/tools/task-tool.js +0 -161
- package/dist/skills/tools/team-tool.js +0 -105
- package/dist/skills/tools/tool-search-tool.js +0 -110
- package/dist/skills/tools/tts-tool.js +0 -45
- package/dist/skills/tools/video-edit-tool.js +0 -74
- package/dist/skills/tools/video-generate-tool.js +0 -66
- package/dist/skills/tools/video-merge-tool.js +0 -92
- package/dist/skills/tools/video-upscale-tool.js +0 -52
- package/dist/skills/tools/web-fetch-tool.js +0 -92
- package/dist/skills/tools/web-search-tool.js +0 -86
- package/dist/skills/tools/worktree-tool.js +0 -147
- package/dist/skills/tools/write-tool.js +0 -81
- /package/dist/{agent → types/agent}/agent.d.ts +0 -0
- /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
- /package/dist/{agent → types/agent}/types.d.ts +0 -0
- /package/dist/{cli → types/cli}/main.d.ts +0 -0
- /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
- /package/dist/{config → types/config}/config.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
- /package/dist/{llm → types/llm}/index.d.ts +0 -0
- /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
- /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
- /package/dist/{llm → types/llm}/transport.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
- /package/dist/{skills → types/skills}/index.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
- /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
|
-
}
|