foliko 1.0.87 → 1.1.1
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/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +325 -21
- package/.agent/data/plugins-state.json +194 -162
- package/.agent/data/puppeteer-sessions/undefined.json +6 -0
- package/.agent/mcp_config.json +0 -1
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins/poster-plugin/README.md +304 -0
- package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +13 -0
- package/.agent/plugins/poster-plugin/package.json +28 -0
- package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
- package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
- package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
- package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
- package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
- package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
- package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
- package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
- package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
- package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
- package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
- package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
- package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
- package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
- package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
- package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
- package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
- package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
- package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +63 -0
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
- package/.agent/plugins/poster-plugin/src/elements/text.js +107 -0
- package/.agent/plugins/poster-plugin/src/fonts.js +233 -0
- package/.agent/plugins/poster-plugin/src/index.js +1658 -0
- package/.agent/plugins/poster-plugin/src/presets.js +36 -0
- package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
- package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
- package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
- package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
- package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
- package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
- package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/index.js +1 -1
- package/.agent/plugins.json +5 -11
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/sessions/cli_default.json +419 -0
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +2195 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +20 -8
- package/.env.example +56 -56
- package/CLAUDE.md +144 -108
- package/README.md +441 -441
- package/calc_tokens_weixin.js +81 -0
- package/cli/src/commands/chat.js +2 -1
- package/docs/CONTEXT_DESIGN.md +1596 -0
- package/examples/test-concurrent-chat.js +60 -60
- package/foliko-creative-3.png +0 -0
- package/foliko-creative-4.png +0 -0
- package/foliko-creative-5.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +2 -1
- package/plugins/extension-executor-plugin.js +91 -2
- package/plugins/file-system-plugin.js +2 -2
- package/plugins/memory-plugin.js +984 -0
- package/plugins/session-plugin.js +57 -1
- package/plugins/weixin-plugin.js +33 -23
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/poster-guide/SKILL.md +1059 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/skills/skill-guide/SKILL.md +130 -108
- package/src/capabilities/skill-manager.js +99 -0
- package/src/core/agent-chat.js +620 -141
- package/src/core/agent-context.js +188 -0
- package/src/core/agent.js +6 -2
- package/src/core/context-manager.js +283 -0
- package/src/core/framework.js +264 -3
- package/src/core/plugin-manager.js +79 -2
- package/src/core/request-context.js +98 -0
- package/src/core/session-context.js +341 -0
- package/src/core/session-storage.js +274 -0
- package/src/executors/mcp-executor.js +2 -2
- package/src/utils/index.js +239 -67
- package/src/utils/plugin-helpers.js +17 -0
- package/story-cover-book-v2.png +0 -0
- package/story-cover-japanese-1.png +0 -0
- package/story-cover-japanese-2.png +0 -0
- package/story-cover-japanese-3.png +0 -0
- package/story-cover-moran.png +0 -0
- package/undefined.png +0 -0
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
- package/.agent/agents/code-assistant.json +0 -14
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -15
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/data/ambient/goals.json +0 -50
- package/.agent/data/ambient/memories.json +0 -7
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/package.json +0 -8
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/daytona/README.md +0 -89
- package/.agent/plugins/daytona/index.js +0 -377
- package/.agent/plugins/daytona/package.json +0 -12
- package/.agent/plugins/marknative/README.md +0 -134
- package/.agent/plugins/marknative/index.js +0 -228
- package/.agent/plugins/marknative/package.json +0 -12
- package/.agent/plugins/marknative/update-readme.js +0 -134
- package/.agent/plugins/system-info/index.js +0 -387
- package/.agent/plugins/system-info/package.json +0 -4
- package/.agent/plugins/system-info/test.js +0 -40
- package/.agent/plugins/temp-repo/LICENSE +0 -201
- package/.agent/plugins/test_plugin.py +0 -304
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/skills/agent-browser/SKILL.md +0 -311
- package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
- package/.agent/skills/sysinfo/SKILL.md +0 -38
- package/.agent/skills/sysinfo/system-info.sh +0 -130
- package/.agent/skills/workflow/SKILL.md +0 -324
- package/.agent/workflows/email-digest.json +0 -50
- package/.agent/workflows/file-backup.json +0 -21
- package/.agent/workflows/get-ip-notify.json +0 -32
- package/.agent/workflows/news-aggregator.json +0 -93
- package/.agent/workflows/news-dashboard-v2.json +0 -94
- package/.agent/workflows/notification-batch.json +0 -32
- package/examples/test-chat-debug.js +0 -102
- package/examples/test-chat-result.js +0 -76
- package/examples/test-chat-stream-diff.js +0 -63
- /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/README.md +0 -0
- /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/package.json +0 -0
|
@@ -0,0 +1,1596 @@
|
|
|
1
|
+
# 分层上下文架构设计方案
|
|
2
|
+
|
|
3
|
+
## 一、当前问题分析
|
|
4
|
+
|
|
5
|
+
### 1.1 核心问题
|
|
6
|
+
|
|
7
|
+
| 问题 | 位置 | 描述 |
|
|
8
|
+
| --------------------------------- | --------------------------- | --------------------------------------------------- |
|
|
9
|
+
| **共享 `_messages`** | `agent-chat.js:100` | `this._messages = []` 是实例级别,所有 session 共用 |
|
|
10
|
+
| **并行 session 冲突** | `_doChat()` | 多个 session 并行时,`_messages` 互相覆盖 |
|
|
11
|
+
| **Session 历史加载到共享内存** | `_loadHistoryFromSession()` | 加载历史到共享的 `_messages`,导致竞态条件 |
|
|
12
|
+
| **上下文层次不清晰** | `runWithContext()` | `context` 只有 `{sessionId, isStream}`,缺少分层 |
|
|
13
|
+
| **Handler 与 SessionPlugin 耦合** | `_loadHistoryFromSession()` | 直接依赖 `sessionPlugin.getHistory()` |
|
|
14
|
+
|
|
15
|
+
### 1.2 问题代码示例
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// agent-chat.js:100 - 共享的消息存储
|
|
19
|
+
this._messages = []; // 问题:所有 session 共用!
|
|
20
|
+
|
|
21
|
+
// agent-chat.js:899-904 - 并行时的竞态
|
|
22
|
+
if (sessionId) {
|
|
23
|
+
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
24
|
+
if (savedMessages.length > 0) {
|
|
25
|
+
this._messages = savedMessages; // 问题:覆盖其他 session 的消息
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 1.3 架构问题图示
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
当前架构:
|
|
34
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
35
|
+
│ AgentChatHandler │
|
|
36
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
37
|
+
│ │ this._messages[] ←── 所有 session 共用! │ │
|
|
38
|
+
│ │ this._sessionQueues ← Per-Session 队列(正确) │ │
|
|
39
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────┘
|
|
41
|
+
│
|
|
42
|
+
▼
|
|
43
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
44
|
+
│ SessionPlugin │
|
|
45
|
+
│ _sessions Map → { messages[], variables, metadata } │
|
|
46
|
+
└─────────────────────────────────────────────────────────────┘
|
|
47
|
+
|
|
48
|
+
并行请求时:
|
|
49
|
+
Request A (session A) ──→ 加载 session A 历史 ──→ _messages = [A的历史]
|
|
50
|
+
│
|
|
51
|
+
Request B (session B) ──→ 加载 session B 历史 ──→ _messages = [B的历史] ← 覆盖!
|
|
52
|
+
│
|
|
53
|
+
Request A 继续 ──→ _messages 被 B 覆盖了! ──→ 保存到 A ──→ A 的历史变成 B 的
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 二、设计目标
|
|
59
|
+
|
|
60
|
+
1. **Per-Session 状态隔离** - 每个 session 有独立的消息存储
|
|
61
|
+
2. **分层上下文** - Request / Session / Agent 三层分离
|
|
62
|
+
3. **可追踪性** - 完整的 requestId、traceId 链路追踪
|
|
63
|
+
4. **向后兼容** - 最小化对现有代码的改动
|
|
64
|
+
5. **可扩展性** - 易于添加新的上下文层、支持多种存储后端
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 三、分层上下文架构
|
|
69
|
+
|
|
70
|
+
### 3.1 层次结构
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
74
|
+
│ Request Context │
|
|
75
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
76
|
+
│ │ requestId - 唯一请求 ID │ │
|
|
77
|
+
│ │ traceId - 分布式追踪 ID │ │
|
|
78
|
+
│ │ parentSpanId - 父 span ID(用于链路追踪) │ │
|
|
79
|
+
│ │ timeout - 请求超时时间 │ │
|
|
80
|
+
│ │ startTime - 请求开始时间 │ │
|
|
81
|
+
│ │ isStream - 是否为流式请求 │ │
|
|
82
|
+
│ │ userId - 用户标识(可选) │ │
|
|
83
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
84
|
+
└─────────────────────────────────────────────────────────────┘
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
88
|
+
│ Session Context │
|
|
89
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
90
|
+
│ │ sessionId - 会话 ID │ │
|
|
91
|
+
│ │ userId - 用户标识 │ │
|
|
92
|
+
│ │ variables - Map<key, value> 会话变量 │ │
|
|
93
|
+
│ │ metadata - 会话元数据 │ │
|
|
94
|
+
│ │ messageStore - Per-Session 消息存储 │ │
|
|
95
|
+
│ │ messages[] - 该 session 的对话历史 │ │
|
|
96
|
+
│ │ historyLoaded - 是否已加载历史 │ │
|
|
97
|
+
│ │ createdAt - 创建时间 │ │
|
|
98
|
+
│ │ lastActive - 最后活跃时间 │ │
|
|
99
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
100
|
+
└─────────────────────────────────────────────────────────────┘
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
104
|
+
│ Agent Context │
|
|
105
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
106
|
+
│ │ agentId - Agent 实例 ID │ │
|
|
107
|
+
│ │ name - Agent 名称 │ │
|
|
108
|
+
│ │ tools - 该 Agent 的工具注册表 │ │
|
|
109
|
+
│ │ skills - 该 Agent 的技能列表 │ │
|
|
110
|
+
│ │ systemPrompt - 系统提示词(动态构建) │ │
|
|
111
|
+
│ │ customData - Agent 级自定义数据 │ │
|
|
112
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
113
|
+
└─────────────────────────────────────────────────────────────┘
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 3.2 AsyncLocalStorage 隔离策略
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// Framework 中的三层 AsyncLocalStorage
|
|
120
|
+
class Framework {
|
|
121
|
+
constructor() {
|
|
122
|
+
// 三层独立的异步上下文存储
|
|
123
|
+
this._requestStorage = new AsyncLocalStorage(); // Request 级别
|
|
124
|
+
this._sessionStorage = new AsyncLocalStorage(); // Session 级别
|
|
125
|
+
this._agentStorage = new AsyncLocalStorage(); // Agent 级别
|
|
126
|
+
|
|
127
|
+
// SessionContext 管理
|
|
128
|
+
this._sessionContexts = new Map(); // sessionId → SessionContext
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 3.3 上下文获取方法
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
class Framework {
|
|
137
|
+
// Request Context
|
|
138
|
+
runWithContext(context, fn) {
|
|
139
|
+
return this._requestStorage.run(context, fn);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getRequestContext() {
|
|
143
|
+
return this._requestStorage.getStore() || null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Session Context
|
|
147
|
+
runInSession(sessionId, fn) {
|
|
148
|
+
const sessionCtx = this.getOrCreateSessionContext(sessionId);
|
|
149
|
+
return this._sessionStorage.run({ sessionContext: sessionCtx }, fn);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getSessionContext() {
|
|
153
|
+
return this._sessionStorage.getStore()?.sessionContext || null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Agent Context
|
|
157
|
+
runWithAgent(agentId, fn) {
|
|
158
|
+
const agentCtx = this._agentContexts.get(agentId);
|
|
159
|
+
return this._agentStorage.run({ agentContext: agentCtx }, fn);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getAgentContext() {
|
|
163
|
+
return this._agentStorage.getStore()?.agentContext || null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 四、核心类设计
|
|
171
|
+
|
|
172
|
+
### 4.1 SessionContext 类
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
/**
|
|
176
|
+
* SessionContext - Session 级别的上下文封装
|
|
177
|
+
*
|
|
178
|
+
* 职责:
|
|
179
|
+
* 1. 管理 Per-Session 的消息存储
|
|
180
|
+
* 2. 管理会话变量(variables)
|
|
181
|
+
* 3. 跟踪会话元数据
|
|
182
|
+
* 4. 处理上下文压缩状态
|
|
183
|
+
*/
|
|
184
|
+
class SessionContext {
|
|
185
|
+
/**
|
|
186
|
+
* @param {string} sessionId - 会话 ID
|
|
187
|
+
* @param {Framework} framework - 框架实例
|
|
188
|
+
* @param {Object} options - 配置选项
|
|
189
|
+
*/
|
|
190
|
+
constructor(sessionId, framework, options = {}) {
|
|
191
|
+
this.sessionId = sessionId;
|
|
192
|
+
this.framework = framework;
|
|
193
|
+
|
|
194
|
+
// 会话变量(可被工具读写)
|
|
195
|
+
this.variables = new Map();
|
|
196
|
+
|
|
197
|
+
// 元数据
|
|
198
|
+
this.metadata = {
|
|
199
|
+
createdAt: Date.now(),
|
|
200
|
+
lastActive: Date.now(),
|
|
201
|
+
messageCount: 0,
|
|
202
|
+
compressionCount: 0,
|
|
203
|
+
...options.metadata,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Per-Session 消息存储
|
|
207
|
+
this.messageStore = {
|
|
208
|
+
messages: [], // 对话历史
|
|
209
|
+
historyLoaded: false, // 是否已从持久化加载
|
|
210
|
+
systemPrompt: null, // 当前系统提示词
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// 压缩状态
|
|
214
|
+
this.compressionState = {
|
|
215
|
+
lastCompressedAt: null,
|
|
216
|
+
lastTokenCount: 0,
|
|
217
|
+
pendingCompression: false,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// 自定义数据插槽
|
|
221
|
+
this.customData = new Map();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ==================== 消息管理 ====================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 获取消息历史
|
|
228
|
+
* @returns {Array} 消息数组
|
|
229
|
+
*/
|
|
230
|
+
getMessages() {
|
|
231
|
+
return this.messageStore.messages;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 添加消息
|
|
236
|
+
* @param {Object} message - 消息对象
|
|
237
|
+
*/
|
|
238
|
+
addMessage(message) {
|
|
239
|
+
this.messageStore.messages.push(message);
|
|
240
|
+
this.metadata.messageCount++;
|
|
241
|
+
this.metadata.lastActive = Date.now();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 添加多条消息
|
|
246
|
+
* @param {Array} messages - 消息数组
|
|
247
|
+
*/
|
|
248
|
+
addMessages(messages) {
|
|
249
|
+
for (const msg of messages) {
|
|
250
|
+
this.addMessage(msg);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 替换所有消息(用于压缩后同步)
|
|
256
|
+
* @param {Array} messages - 新消息数组
|
|
257
|
+
*/
|
|
258
|
+
replaceMessages(messages) {
|
|
259
|
+
this.messageStore.messages = messages;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 清空消息历史
|
|
264
|
+
*/
|
|
265
|
+
clearMessages() {
|
|
266
|
+
this.messageStore.messages = [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ==================== 变量管理 ====================
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 获取变量
|
|
273
|
+
* @param {string} key - 变量名
|
|
274
|
+
* @param {*} defaultValue - 默认值
|
|
275
|
+
* @returns {*}
|
|
276
|
+
*/
|
|
277
|
+
getVariable(key, defaultValue = undefined) {
|
|
278
|
+
return this.variables.get(key) ?? defaultValue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 设置变量
|
|
283
|
+
* @param {string} key - 变量名
|
|
284
|
+
* @param {*} value - 变量值
|
|
285
|
+
*/
|
|
286
|
+
setVariable(key, value) {
|
|
287
|
+
this.variables.set(key, value);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 删除变量
|
|
292
|
+
* @param {string} key - 变量名
|
|
293
|
+
*/
|
|
294
|
+
deleteVariable(key) {
|
|
295
|
+
this.variables.delete(key);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 获取所有变量
|
|
300
|
+
* @returns {Object}
|
|
301
|
+
*/
|
|
302
|
+
getAllVariables() {
|
|
303
|
+
return Object.fromEntries(this.variables);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ==================== 元数据 ====================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 更新最后活跃时间
|
|
310
|
+
*/
|
|
311
|
+
touch() {
|
|
312
|
+
this.metadata.lastActive = Date.now();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 获取会话摘要
|
|
317
|
+
* @returns {Object}
|
|
318
|
+
*/
|
|
319
|
+
getSummary() {
|
|
320
|
+
return {
|
|
321
|
+
sessionId: this.sessionId,
|
|
322
|
+
messageCount: this.metadata.messageCount,
|
|
323
|
+
createdAt: this.metadata.createdAt,
|
|
324
|
+
lastActive: this.metadata.lastActive,
|
|
325
|
+
variables: this.getAllVariables(),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 4.2 RequestContext 类(轻量)
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
/**
|
|
335
|
+
* RequestContext - Request 级别的上下文封装
|
|
336
|
+
*
|
|
337
|
+
* 职责:
|
|
338
|
+
* 1. 追踪请求链路
|
|
339
|
+
* 2. 管理请求级超时
|
|
340
|
+
* 3. 传递请求元数据
|
|
341
|
+
*/
|
|
342
|
+
class RequestContext {
|
|
343
|
+
constructor(options = {}) {
|
|
344
|
+
this.requestId = options.requestId || generateId();
|
|
345
|
+
this.traceId = options.traceId || generateId();
|
|
346
|
+
this.parentSpanId = options.parentSpanId || null;
|
|
347
|
+
|
|
348
|
+
this.timeout = options.timeout || 120000; // 默认 2 分钟
|
|
349
|
+
this.startTime = options.startTime || Date.now();
|
|
350
|
+
|
|
351
|
+
this.isStream = options.isStream || false;
|
|
352
|
+
this.userId = options.userId || null;
|
|
353
|
+
|
|
354
|
+
// 请求级状态
|
|
355
|
+
this.cancelled = false;
|
|
356
|
+
this.abortController = new AbortController();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 检查是否超时
|
|
361
|
+
* @returns {boolean}
|
|
362
|
+
*/
|
|
363
|
+
isTimeout() {
|
|
364
|
+
return Date.now() - this.startTime > this.timeout;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 取消请求
|
|
369
|
+
*/
|
|
370
|
+
cancel() {
|
|
371
|
+
this.cancelled = true;
|
|
372
|
+
this.abortController.abort();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 获取请求耗时(毫秒)
|
|
377
|
+
* @returns {number}
|
|
378
|
+
*/
|
|
379
|
+
getElapsed() {
|
|
380
|
+
return Date.now() - this.startTime;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 4.3 AgentContext 类(轻量)
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
/**
|
|
389
|
+
* AgentContext - Agent 级别的上下文封装
|
|
390
|
+
*
|
|
391
|
+
* 职责:
|
|
392
|
+
* 1. 隔离不同 Agent 实例的状态
|
|
393
|
+
* 2. 管理 Agent 级工具注册
|
|
394
|
+
* 3. 管理 Agent 级技能
|
|
395
|
+
*/
|
|
396
|
+
class AgentContext {
|
|
397
|
+
constructor(agentId, agent) {
|
|
398
|
+
this.agentId = agentId;
|
|
399
|
+
this.agent = agent; // Agent 实例引用
|
|
400
|
+
|
|
401
|
+
// Agent 级工具注册(覆盖全局)
|
|
402
|
+
this.tools = new Map();
|
|
403
|
+
|
|
404
|
+
// Agent 级技能
|
|
405
|
+
this.skills = [];
|
|
406
|
+
|
|
407
|
+
// Agent 级系统提示词
|
|
408
|
+
this.systemPrompt = null;
|
|
409
|
+
|
|
410
|
+
// 自定义数据
|
|
411
|
+
this.customData = new Map();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* 注册工具
|
|
416
|
+
* @param {Object} toolDef - 工具定义
|
|
417
|
+
*/
|
|
418
|
+
registerTool(toolDef) {
|
|
419
|
+
this.tools.set(toolDef.name, toolDef);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 获取工具
|
|
424
|
+
* @param {string} name - 工具名
|
|
425
|
+
* @returns {Object|null}
|
|
426
|
+
*/
|
|
427
|
+
getTool(name) {
|
|
428
|
+
return this.tools.get(name) || null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 获取所有工具
|
|
433
|
+
* @returns {Array}
|
|
434
|
+
*/
|
|
435
|
+
getAllTools() {
|
|
436
|
+
return Array.from(this.tools.values());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 4.4 ContextManager 类
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
/**
|
|
445
|
+
* ContextManager - 上下文管理器
|
|
446
|
+
*
|
|
447
|
+
* 职责:
|
|
448
|
+
* 1. 统一管理所有 Context 的生命周期
|
|
449
|
+
* 2. 提供上下文查询接口
|
|
450
|
+
* 3. 处理上下文清理和资源释放
|
|
451
|
+
*/
|
|
452
|
+
class ContextManager {
|
|
453
|
+
constructor(framework) {
|
|
454
|
+
this.framework = framework;
|
|
455
|
+
|
|
456
|
+
// Context 存储
|
|
457
|
+
this._requestContexts = new Map(); // requestId → RequestContext
|
|
458
|
+
this._sessionContexts = new Map(); // sessionId → SessionContext
|
|
459
|
+
this._agentContexts = new Map(); // agentId → AgentContext
|
|
460
|
+
|
|
461
|
+
// 引用计数(用于自动清理)
|
|
462
|
+
this._refCounts = new Map();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ==================== Request Context ====================
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* 创建 Request Context
|
|
469
|
+
* @param {Object} options
|
|
470
|
+
* @returns {RequestContext}
|
|
471
|
+
*/
|
|
472
|
+
createRequestContext(options = {}) {
|
|
473
|
+
const ctx = new RequestContext(options);
|
|
474
|
+
this._requestContexts.set(ctx.requestId, ctx);
|
|
475
|
+
this._refCounts.set(`request:${ctx.requestId}`, 1);
|
|
476
|
+
return ctx;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* 获取 Request Context
|
|
481
|
+
* @param {string} requestId
|
|
482
|
+
* @returns {RequestContext|null}
|
|
483
|
+
*/
|
|
484
|
+
getRequestContext(requestId) {
|
|
485
|
+
return this._requestContexts.get(requestId) || null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 释放 Request Context
|
|
490
|
+
* @param {string} requestId
|
|
491
|
+
*/
|
|
492
|
+
releaseRequestContext(requestId) {
|
|
493
|
+
const ctx = this._requestContexts.get(requestId);
|
|
494
|
+
if (ctx) {
|
|
495
|
+
ctx.cancel();
|
|
496
|
+
this._requestContexts.delete(requestId);
|
|
497
|
+
this._refCounts.delete(`request:${requestId}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ==================== Session Context ====================
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 获取或创建 Session Context
|
|
505
|
+
* @param {string} sessionId
|
|
506
|
+
* @param {Object} options
|
|
507
|
+
* @returns {SessionContext}
|
|
508
|
+
*/
|
|
509
|
+
getOrCreateSessionContext(sessionId, options = {}) {
|
|
510
|
+
let ctx = this._sessionContexts.get(sessionId);
|
|
511
|
+
if (!ctx) {
|
|
512
|
+
ctx = new SessionContext(sessionId, this.framework, options);
|
|
513
|
+
this._sessionContexts.set(sessionId, ctx);
|
|
514
|
+
this._refCounts.set(`session:${sessionId}`, 1);
|
|
515
|
+
} else {
|
|
516
|
+
// 增加引用计数
|
|
517
|
+
this._refCounts.set(
|
|
518
|
+
`session:${sessionId}`,
|
|
519
|
+
(this._refCounts.get(`session:${sessionId}`) || 0) + 1
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return ctx;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 获取 Session Context
|
|
527
|
+
* @param {string} sessionId
|
|
528
|
+
* @returns {SessionContext|null}
|
|
529
|
+
*/
|
|
530
|
+
getSessionContext(sessionId) {
|
|
531
|
+
return this._sessionContexts.get(sessionId) || null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 释放 Session Context
|
|
536
|
+
* @param {string} sessionId
|
|
537
|
+
*/
|
|
538
|
+
releaseSessionContext(sessionId) {
|
|
539
|
+
const count = this._refCounts.get(`session:${sessionId}`) || 0;
|
|
540
|
+
if (count <= 1) {
|
|
541
|
+
this._sessionContexts.delete(sessionId);
|
|
542
|
+
this._refCounts.delete(`session:${sessionId}`);
|
|
543
|
+
} else {
|
|
544
|
+
this._refCounts.set(`session:${sessionId}`, count - 1);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ==================== 统计 ====================
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 获取统计信息
|
|
552
|
+
* @returns {Object}
|
|
553
|
+
*/
|
|
554
|
+
getStats() {
|
|
555
|
+
return {
|
|
556
|
+
activeRequests: this._requestContexts.size,
|
|
557
|
+
activeSessions: this._sessionContexts.size,
|
|
558
|
+
activeAgents: this._agentContexts.size,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 五、Framework 改造
|
|
567
|
+
|
|
568
|
+
### 5.1 新增方法
|
|
569
|
+
|
|
570
|
+
```javascript
|
|
571
|
+
// src/core/framework.js 新增
|
|
572
|
+
|
|
573
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
574
|
+
|
|
575
|
+
class Framework extends EventEmitter {
|
|
576
|
+
constructor(config = {}) {
|
|
577
|
+
super();
|
|
578
|
+
// ... 现有代码 ...
|
|
579
|
+
|
|
580
|
+
// 三层 AsyncLocalStorage
|
|
581
|
+
this._requestStorage = new AsyncLocalStorage();
|
|
582
|
+
this._sessionStorage = new AsyncLocalStorage();
|
|
583
|
+
this._agentStorage = new AsyncLocalStorage();
|
|
584
|
+
|
|
585
|
+
// Context 管理器
|
|
586
|
+
this._contextManager = new ContextManager(this);
|
|
587
|
+
|
|
588
|
+
// SessionContext 缓存(轻量管理,不依赖 ContextManager)
|
|
589
|
+
this._sessionContexts = new Map();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ==================== Request Context ====================
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* 在 Request 上下文中执行函数
|
|
596
|
+
* @param {Object} context - Request 上下文
|
|
597
|
+
* @param {Function} fn - 异步函数
|
|
598
|
+
* @returns {Promise}
|
|
599
|
+
*/
|
|
600
|
+
runWithContext(context, fn) {
|
|
601
|
+
return this._requestStorage.run(context, fn);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* 获取当前 Request 上下文
|
|
606
|
+
* @returns {Object|null}
|
|
607
|
+
*/
|
|
608
|
+
getRequestContext() {
|
|
609
|
+
return this._requestStorage.getStore() || null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* 创建 Request Context 并执行
|
|
614
|
+
* @param {Object} options - Request 选项
|
|
615
|
+
* @param {Function} fn - 异步函数
|
|
616
|
+
* @returns {Promise}
|
|
617
|
+
*/
|
|
618
|
+
async withRequest(options, fn) {
|
|
619
|
+
const requestCtx = new RequestContext(options);
|
|
620
|
+
return this._requestStorage.run(requestCtx, fn);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ==================== Session Context ====================
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* 获取或创建 SessionContext
|
|
627
|
+
* @param {string} sessionId - 会话 ID
|
|
628
|
+
* @param {Object} options - 配置选项
|
|
629
|
+
* @returns {SessionContext}
|
|
630
|
+
*/
|
|
631
|
+
getOrCreateSessionContext(sessionId, options = {}) {
|
|
632
|
+
let ctx = this._sessionContexts.get(sessionId);
|
|
633
|
+
if (!ctx) {
|
|
634
|
+
ctx = new SessionContext(sessionId, this, options);
|
|
635
|
+
this._sessionContexts.set(sessionId, ctx);
|
|
636
|
+
this.emit('session:context-created', ctx);
|
|
637
|
+
}
|
|
638
|
+
return ctx;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* 在 Session 上下文中执行函数
|
|
643
|
+
* @param {string} sessionId - 会话 ID
|
|
644
|
+
* @param {Object} options - Session 选项
|
|
645
|
+
* @param {Function} fn - 异步函数
|
|
646
|
+
* @returns {Promise}
|
|
647
|
+
*/
|
|
648
|
+
async runInSession(sessionId, options, fn) {
|
|
649
|
+
const sessionCtx = this.getOrCreateSessionContext(sessionId, options);
|
|
650
|
+
const context = { sessionContext: sessionCtx, sessionId };
|
|
651
|
+
return this._sessionStorage.run(context, fn);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* 获取当前 Session 上下文(便捷方法)
|
|
656
|
+
* @returns {SessionContext|null}
|
|
657
|
+
*/
|
|
658
|
+
getSessionContext() {
|
|
659
|
+
const store = this._sessionStorage.getStore();
|
|
660
|
+
return store?.sessionContext || null;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 获取当前 sessionId(便捷方法)
|
|
665
|
+
* @returns {string|null}
|
|
666
|
+
*/
|
|
667
|
+
getCurrentSessionId() {
|
|
668
|
+
const store = this._sessionStorage.getStore();
|
|
669
|
+
return store?.sessionId || null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* 销毁 Session Context
|
|
674
|
+
* @param {string} sessionId
|
|
675
|
+
*/
|
|
676
|
+
destroySessionContext(sessionId) {
|
|
677
|
+
if (this._sessionContexts.has(sessionId)) {
|
|
678
|
+
const ctx = this._sessionContexts.get(sessionId);
|
|
679
|
+
this._sessionContexts.delete(sessionId);
|
|
680
|
+
this.emit('session:context-destroyed', ctx);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ==================== Agent Context ====================
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* 获取或创建 AgentContext
|
|
688
|
+
* @param {string} agentId
|
|
689
|
+
* @param {Agent} agent
|
|
690
|
+
* @returns {AgentContext}
|
|
691
|
+
*/
|
|
692
|
+
getOrCreateAgentContext(agentId, agent) {
|
|
693
|
+
if (!this._agentContexts) {
|
|
694
|
+
this._agentContexts = new Map();
|
|
695
|
+
}
|
|
696
|
+
let ctx = this._agentContexts.get(agentId);
|
|
697
|
+
if (!ctx) {
|
|
698
|
+
ctx = new AgentContext(agentId, agent);
|
|
699
|
+
this._agentContexts.set(agentId, ctx);
|
|
700
|
+
}
|
|
701
|
+
return ctx;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 在 Agent 上下文中执行函数
|
|
706
|
+
* @param {string} agentId
|
|
707
|
+
* @param {Function} fn
|
|
708
|
+
* @returns {Promise}
|
|
709
|
+
*/
|
|
710
|
+
runWithAgent(agentId, fn) {
|
|
711
|
+
const ctx = this._agentContexts.get(agentId);
|
|
712
|
+
if (!ctx) {
|
|
713
|
+
throw new Error(`AgentContext not found: ${agentId}`);
|
|
714
|
+
}
|
|
715
|
+
return this._agentStorage.run({ agentContext: ctx, agentId }, fn);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* 获取当前 Agent 上下文
|
|
720
|
+
* @returns {AgentContext|null}
|
|
721
|
+
*/
|
|
722
|
+
getAgentContext() {
|
|
723
|
+
const store = this._agentStorage.getStore();
|
|
724
|
+
return store?.agentContext || null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ==================== 组合上下文 ====================
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 在完整上下文中执行(Request + Session + Agent)
|
|
731
|
+
* @param {Object} requestOptions - Request 选项
|
|
732
|
+
* @param {string} sessionId - Session ID
|
|
733
|
+
* @param {string} agentId - Agent ID
|
|
734
|
+
* @param {Function} fn - 异步函数
|
|
735
|
+
* @returns {Promise}
|
|
736
|
+
*/
|
|
737
|
+
async runInFullContext(requestOptions, sessionId, agentId, fn) {
|
|
738
|
+
const requestCtx = new RequestContext(requestOptions);
|
|
739
|
+
const sessionCtx = this.getOrCreateSessionContext(sessionId);
|
|
740
|
+
const agentCtx = this.getOrCreateAgentContext(
|
|
741
|
+
agentId,
|
|
742
|
+
this._agents.find((a) => a.id === agentId)
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
const requestStore = { ...requestCtx };
|
|
746
|
+
const sessionStore = { sessionContext: sessionCtx, sessionId };
|
|
747
|
+
const agentStore = { agentContext: agentCtx, agentId };
|
|
748
|
+
|
|
749
|
+
return this._requestStorage.run(requestStore, () => {
|
|
750
|
+
return this._sessionStorage.run(sessionStore, () => {
|
|
751
|
+
return this._agentStorage.run(agentStore, fn);
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## 六、AgentChatHandler 改造
|
|
761
|
+
|
|
762
|
+
### 6.1 Per-Session 消息存储
|
|
763
|
+
|
|
764
|
+
```javascript
|
|
765
|
+
// agent-chat.js 改造
|
|
766
|
+
|
|
767
|
+
class AgentChatHandler extends EventEmitter {
|
|
768
|
+
constructor(agent, config = {}) {
|
|
769
|
+
super();
|
|
770
|
+
|
|
771
|
+
// 移除共享的 _messages
|
|
772
|
+
// this._messages = []; // 删除!
|
|
773
|
+
|
|
774
|
+
// 保留队列机制(已经是 Per-Session,正确)
|
|
775
|
+
this._sessionQueues = new Map();
|
|
776
|
+
this._processingSessions = new Set();
|
|
777
|
+
|
|
778
|
+
// Per-Session 消息存储(替代原来的 _messages)
|
|
779
|
+
// 结构:sessionId → { messages: [], historyLoaded: false, compressionState: {} }
|
|
780
|
+
this._sessionMessageStores = new Map();
|
|
781
|
+
|
|
782
|
+
// ...
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* 获取当前 session 的消息存储
|
|
787
|
+
* @private
|
|
788
|
+
* @returns {Object} messageStore
|
|
789
|
+
*/
|
|
790
|
+
_getSessionMessageStore(sessionId) {
|
|
791
|
+
if (!sessionId) {
|
|
792
|
+
// 无 session 的单次请求,使用临时存储
|
|
793
|
+
return { messages: [], historyLoaded: false };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (!this._sessionMessageStores.has(sessionId)) {
|
|
797
|
+
this._sessionMessageStores.set(sessionId, {
|
|
798
|
+
messages: [],
|
|
799
|
+
historyLoaded: false,
|
|
800
|
+
compressionState: {
|
|
801
|
+
lastCompressedAt: null,
|
|
802
|
+
lastTokenCount: 0,
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return this._sessionMessageStores.get(sessionId);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 从 session 存储加载聊天历史
|
|
811
|
+
* @param {string} sessionId
|
|
812
|
+
* @returns {Array}
|
|
813
|
+
* @private
|
|
814
|
+
*/
|
|
815
|
+
_loadHistoryFromSession(sessionId) {
|
|
816
|
+
if (!sessionId) return [];
|
|
817
|
+
|
|
818
|
+
try {
|
|
819
|
+
// 优先从 SessionContext 获取
|
|
820
|
+
const sessionCtx = this.framework.getSessionContext();
|
|
821
|
+
if (sessionCtx && sessionCtx.sessionId === sessionId) {
|
|
822
|
+
const messages = sessionCtx.getMessages();
|
|
823
|
+
if (messages && messages.length > 0) {
|
|
824
|
+
return messages;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// 回退到 SessionPlugin
|
|
829
|
+
const sessionPlugin = this.agent.framework.pluginManager.get('session');
|
|
830
|
+
if (!sessionPlugin) return [];
|
|
831
|
+
|
|
832
|
+
const messages = sessionPlugin.getHistory(sessionId);
|
|
833
|
+
return messages || [];
|
|
834
|
+
} catch (err) {
|
|
835
|
+
// 忽略加载错误
|
|
836
|
+
}
|
|
837
|
+
return [];
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* 保存聊天历史到 session 存储
|
|
842
|
+
* @param {string} sessionId
|
|
843
|
+
* @param {Array} messages
|
|
844
|
+
* @private
|
|
845
|
+
*/
|
|
846
|
+
_saveHistoryToSession(sessionId, messages) {
|
|
847
|
+
if (!sessionId) return;
|
|
848
|
+
|
|
849
|
+
try {
|
|
850
|
+
// 优先保存到 SessionContext
|
|
851
|
+
const sessionCtx = this.framework.getSessionContext();
|
|
852
|
+
if (sessionCtx && sessionCtx.sessionId === sessionId) {
|
|
853
|
+
sessionCtx.replaceMessages(messages);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// 回退到 SessionPlugin
|
|
858
|
+
const sessionPlugin = this.agent.framework.pluginManager.get('session');
|
|
859
|
+
if (!sessionPlugin) return;
|
|
860
|
+
|
|
861
|
+
const cleanedMessages = prepareMessagesForAPI(messages);
|
|
862
|
+
sessionPlugin.replaceMessages(sessionId, cleanedMessages);
|
|
863
|
+
} catch (err) {
|
|
864
|
+
// 忽略保存错误
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* 执行实际聊天逻辑
|
|
870
|
+
* @private
|
|
871
|
+
*/
|
|
872
|
+
async _doChat(message, options = {}) {
|
|
873
|
+
const sessionId = options.sessionId || null;
|
|
874
|
+
const framework = this.agent.framework;
|
|
875
|
+
|
|
876
|
+
// 获取当前 session 的消息存储
|
|
877
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
878
|
+
const messages = messageStore.messages; // 使用 Per-Session 的消息数组
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
// 从 session 加载聊天历史(只加载一次)
|
|
882
|
+
if (sessionId && !messageStore.historyLoaded) {
|
|
883
|
+
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
884
|
+
if (savedMessages.length > 0) {
|
|
885
|
+
messages.push(...savedMessages);
|
|
886
|
+
messageStore.historyLoaded = true;
|
|
887
|
+
logger.info(`Loaded ${savedMessages.length} messages from session ${sessionId}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 刷新系统提示词
|
|
892
|
+
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
893
|
+
|
|
894
|
+
const userMessage =
|
|
895
|
+
typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
896
|
+
messages.push(userMessage);
|
|
897
|
+
|
|
898
|
+
// 检查是否需要压缩
|
|
899
|
+
const totalTokens = this._countMessagesTokens(messages);
|
|
900
|
+
if (totalTokens > this._maxContextTokens * 0.7) {
|
|
901
|
+
await this._compressContext(sessionId, messages, messageStore);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// 构建上下文对象
|
|
905
|
+
const requestCtx = framework.getRequestContext() || {};
|
|
906
|
+
const context = {
|
|
907
|
+
...requestCtx,
|
|
908
|
+
sessionId,
|
|
909
|
+
sessionContext: framework.getSessionContext(),
|
|
910
|
+
isStream: false,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// 使用 runWithContext 执行
|
|
914
|
+
const result = await framework.runWithContext(context, async () => {
|
|
915
|
+
// 使用 Per-Session 的消息数组
|
|
916
|
+
return agent.generate({ messages, ...this.providerOptions });
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
messages.push(...result.response.messages);
|
|
920
|
+
|
|
921
|
+
// 保存聊天历史
|
|
922
|
+
if (sessionId) {
|
|
923
|
+
this._saveHistoryToSession(sessionId, messages);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
success: true,
|
|
928
|
+
message: result.text || '',
|
|
929
|
+
stepCount: result.stepCount || 1,
|
|
930
|
+
};
|
|
931
|
+
} catch (err) {
|
|
932
|
+
this.emit('error', { error: err.message });
|
|
933
|
+
throw err;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* 压缩上下文
|
|
939
|
+
* @private
|
|
940
|
+
*/
|
|
941
|
+
async _compressContext(sessionId, messages, messageStore) {
|
|
942
|
+
// 使用传入的 messages 引用,直接修改
|
|
943
|
+
// ... 压缩逻辑保持不变,只是不再操作 this._messages
|
|
944
|
+
// ...
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## 七、使用示例
|
|
952
|
+
|
|
953
|
+
### 7.1 完整请求流程
|
|
954
|
+
|
|
955
|
+
```javascript
|
|
956
|
+
// 创建请求
|
|
957
|
+
const requestId = generateId();
|
|
958
|
+
|
|
959
|
+
// 在完整上下文中执行
|
|
960
|
+
await framework.runInFullContext(
|
|
961
|
+
{ requestId, timeout: 60000, isStream: false },
|
|
962
|
+
sessionId,
|
|
963
|
+
agentId,
|
|
964
|
+
async () => {
|
|
965
|
+
// 在这里可以获取任何层级的上下文
|
|
966
|
+
const reqCtx = framework.getRequestContext();
|
|
967
|
+
const sessCtx = framework.getSessionContext();
|
|
968
|
+
const agentCtx = framework.getAgentContext();
|
|
969
|
+
|
|
970
|
+
console.log(`Request ${reqCtx.requestId}, Session ${sessCtx.sessionId}`);
|
|
971
|
+
|
|
972
|
+
// Agent 执行
|
|
973
|
+
const result = await agent.chat('Hello');
|
|
974
|
+
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
### 7.2 工具中访问上下文
|
|
981
|
+
|
|
982
|
+
```javascript
|
|
983
|
+
// 在工具执行时获取 session 信息
|
|
984
|
+
framework.registerTool({
|
|
985
|
+
name: 'get_session_info',
|
|
986
|
+
description: 'Get current session information',
|
|
987
|
+
inputSchema: z.object({}),
|
|
988
|
+
execute: async (args, framework) => {
|
|
989
|
+
const sessionCtx = framework.getSessionContext();
|
|
990
|
+
const requestCtx = framework.getRequestContext();
|
|
991
|
+
|
|
992
|
+
return {
|
|
993
|
+
sessionId: sessionCtx?.sessionId,
|
|
994
|
+
messageCount: sessionCtx?.metadata.messageCount,
|
|
995
|
+
requestId: requestCtx?.requestId,
|
|
996
|
+
elapsed: requestCtx?.getElapsed(),
|
|
997
|
+
};
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
### 7.3 Workflow 中使用
|
|
1003
|
+
|
|
1004
|
+
```javascript
|
|
1005
|
+
// workflow-engine.js
|
|
1006
|
+
async executeStep(step, context) {
|
|
1007
|
+
const sessionId = context.variables?._sessionId;
|
|
1008
|
+
|
|
1009
|
+
// 在 session 上下文中执行
|
|
1010
|
+
await this.framework.runInSession(sessionId, {}, async () => {
|
|
1011
|
+
const sessionCtx = this.framework.getSessionContext();
|
|
1012
|
+
sessionCtx.setVariable('workflow_state', state);
|
|
1013
|
+
|
|
1014
|
+
// 执行工具
|
|
1015
|
+
const result = await this.framework.executeTool(step.tool, args);
|
|
1016
|
+
|
|
1017
|
+
// 更新状态
|
|
1018
|
+
state = sessionCtx.getVariable('workflow_state');
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
## 八、向后兼容
|
|
1026
|
+
|
|
1027
|
+
### 8.1 保留原有接口
|
|
1028
|
+
|
|
1029
|
+
```javascript
|
|
1030
|
+
// Framework 保留原有方法,但标记为 deprecated
|
|
1031
|
+
class Framework {
|
|
1032
|
+
/**
|
|
1033
|
+
* @deprecated 使用 runInSession 代替
|
|
1034
|
+
*/
|
|
1035
|
+
runWithContext(context, fn) {
|
|
1036
|
+
console.warn('runWithContext is deprecated, use runInSession for session context');
|
|
1037
|
+
// 保持原有逻辑兼容
|
|
1038
|
+
return this._requestStorage.run(context, fn);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* @deprecated 使用 getSessionContext 代替
|
|
1043
|
+
*/
|
|
1044
|
+
getExecutionContext() {
|
|
1045
|
+
console.warn('getExecutionContext is deprecated, use getSessionContext');
|
|
1046
|
+
const store = this._requestStorage.getStore();
|
|
1047
|
+
if (store?.sessionContext) {
|
|
1048
|
+
return { sessionId: store.sessionContext.sessionId };
|
|
1049
|
+
}
|
|
1050
|
+
return store || { sessionId: null };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
### 8.2 SessionPlugin 兼容
|
|
1056
|
+
|
|
1057
|
+
```javascript
|
|
1058
|
+
// SessionPlugin 继续提供原有接口,但内部可以使用 SessionContext
|
|
1059
|
+
class SessionPlugin {
|
|
1060
|
+
getHistory(sessionId) {
|
|
1061
|
+
// 优先从 SessionContext 获取
|
|
1062
|
+
const ctx = this._framework.getSessionContext();
|
|
1063
|
+
if (ctx && ctx.sessionId === sessionId) {
|
|
1064
|
+
return ctx.getMessages();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// 回退到原有存储
|
|
1068
|
+
const session = this._sessions.get(sessionId);
|
|
1069
|
+
return session?.messages || [];
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
## 九、迁移步骤
|
|
1077
|
+
|
|
1078
|
+
### Phase 1: 引入 SessionContext(✅ 已完成)
|
|
1079
|
+
|
|
1080
|
+
1. ✅ 创建 `src/core/session-context.js`
|
|
1081
|
+
2. ✅ 创建 `src/core/request-context.js`
|
|
1082
|
+
3. ✅ 创建 `src/core/agent-context.js`
|
|
1083
|
+
4. ✅ 创建 `src/core/context-manager.js`
|
|
1084
|
+
5. ✅ 在 Framework 中添加三层 AsyncLocalStorage
|
|
1085
|
+
6. ✅ 实现 `getOrCreateSessionContext()` 和 `getSessionContext()`
|
|
1086
|
+
7. ✅ **不修改** `AgentChatHandler`,保持原有 `_messages` 逻辑
|
|
1087
|
+
|
|
1088
|
+
**完成时间**: 2026-04-06
|
|
1089
|
+
**验证**: Bootstrap 测试通过,新组件正常工作
|
|
1090
|
+
|
|
1091
|
+
### Phase 2: AgentChatHandler Per-Session 改造(✅ 已完成)
|
|
1092
|
+
|
|
1093
|
+
1. ✅ 在 `AgentChatHandler` 中添加 `_sessionMessageStores` Map
|
|
1094
|
+
2. ✅ 修改 `_doChat` 使用 Per-Session 的消息数组
|
|
1095
|
+
3. ✅ 修改 `_compressContext` 使用传入的 messages 引用
|
|
1096
|
+
4. ✅ 修改 `_simpleCompress` 使用传入的 messages 引用
|
|
1097
|
+
5. ✅ 修改 `chatStream` 使用 Per-Session 消息数组
|
|
1098
|
+
6. ✅ 修改 `clearHistory` 使用 Per-Session 消息存储
|
|
1099
|
+
7. ✅ 验证多 session 并行不冲突
|
|
1100
|
+
|
|
1101
|
+
**完成时间**: 2026-04-06
|
|
1102
|
+
**验证**: Per-Session 消息隔离测试通过,clearHistory 测试通过
|
|
1103
|
+
|
|
1104
|
+
### Phase 3: 移除 SessionPlugin 依赖(✅ 已完成)
|
|
1105
|
+
|
|
1106
|
+
1. ✅ `_loadHistoryFromSession` 直接使用 `framework.getOrCreateSessionContext()`
|
|
1107
|
+
2. ✅ `_saveHistoryToSession` 直接使用 `framework.getOrCreateSessionContext()`
|
|
1108
|
+
3. ✅ `clearHistory` 中使用 SessionContext 替代 SessionPlugin
|
|
1109
|
+
4. ✅ 验证测试通过
|
|
1110
|
+
|
|
1111
|
+
**完成时间**: 2026-04-06
|
|
1112
|
+
**验证**: Bootstrap 测试通过,SessionContext 方法测试通过
|
|
1113
|
+
|
|
1114
|
+
### Phase 4: 文档和示例(✅ 已完成)
|
|
1115
|
+
|
|
1116
|
+
1. ✅ 更新 CLAUDE.md 中的架构说明
|
|
1117
|
+
2. ✅ 更新 CLAUDE.md 中的 Key Patterns
|
|
1118
|
+
3. ✅ 集成测试通过
|
|
1119
|
+
|
|
1120
|
+
**完成时间**: 2026-04-06
|
|
1121
|
+
**验证**: 集成测试全部通过
|
|
1122
|
+
|
|
1123
|
+
---
|
|
1124
|
+
|
|
1125
|
+
## 十、风险和注意事项
|
|
1126
|
+
|
|
1127
|
+
### 10.1 AsyncLocalStorage 嵌套限制
|
|
1128
|
+
|
|
1129
|
+
- Node.js 的 AsyncLocalStorage 不支持嵌套相同的 storage 实例
|
|
1130
|
+
- 需要使用不同的 storage 实例隔离不同层级
|
|
1131
|
+
|
|
1132
|
+
### 10.2 SessionContext 内存泄漏
|
|
1133
|
+
|
|
1134
|
+
- `_sessionContexts` Map 需要在 session 过期时清理
|
|
1135
|
+
- 需要与 SessionPlugin 的 TTL 机制协同
|
|
1136
|
+
|
|
1137
|
+
### 10.3 向后兼容
|
|
1138
|
+
|
|
1139
|
+
- 现有代码使用 `getExecutionContext()` 获取 sessionId
|
|
1140
|
+
- 需要保留此方法但指向新的 SessionContext
|
|
1141
|
+
|
|
1142
|
+
---
|
|
1143
|
+
|
|
1144
|
+
## 十一、存储抽象层设计
|
|
1145
|
+
|
|
1146
|
+
### 11.1 设计目标
|
|
1147
|
+
|
|
1148
|
+
支持多种存储后端(内存、文件、JSON、数据库),通过接口抽象实现可插拔。
|
|
1149
|
+
|
|
1150
|
+
### 11.2 架构图
|
|
1151
|
+
|
|
1152
|
+
```
|
|
1153
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
1154
|
+
│ SessionContext │
|
|
1155
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
1156
|
+
│ │ messages[], variables, metadata... │ │
|
|
1157
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
1158
|
+
│ │ │
|
|
1159
|
+
│ ▼ │
|
|
1160
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
1161
|
+
│ │ SessionStorageAdapter │ │
|
|
1162
|
+
│ │ ┌─────────────────────────────────────────────┐ │ │
|
|
1163
|
+
│ │ │ interface SessionStorage { │ │ │
|
|
1164
|
+
│ │ │ async load(sessionId): SessionData │ │ │
|
|
1165
|
+
│ │ │ async save(sessionId, data): void │ │ │
|
|
1166
|
+
│ │ │ async delete(sessionId): void │ │ │
|
|
1167
|
+
│ │ │ async list(): string[] │ │ │
|
|
1168
|
+
│ │ │ } │ │ │
|
|
1169
|
+
│ │ └─────────────────────────────────────────────┘ │ │
|
|
1170
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
1171
|
+
│ │ │
|
|
1172
|
+
│ ┌────────────────────┼────────────────────┐ │
|
|
1173
|
+
│ ▼ ▼ ▼ │
|
|
1174
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
1175
|
+
│ │MemoryStorage│ │ FileStorage │ │ JsonStorage │ │
|
|
1176
|
+
│ │ (默认) │ │ (持久化) │ │ (可迁移) │ │
|
|
1177
|
+
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
1178
|
+
│ │
|
|
1179
|
+
│ ┌────────────────────┐ │
|
|
1180
|
+
│ ▼ ▼ │
|
|
1181
|
+
│ ┌─────────────┐ ┌─────────────┐ │
|
|
1182
|
+
│ │SQLiteStorage│ │MongoStorage │ (未来扩展) │
|
|
1183
|
+
│ └─────────────┘ └─────────────┘ │
|
|
1184
|
+
└─────────────────────────────────────────────────────────────┘
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
### 11.3 接口定义
|
|
1188
|
+
|
|
1189
|
+
```javascript
|
|
1190
|
+
/**
|
|
1191
|
+
* Session 存储接口
|
|
1192
|
+
* 所有存储后端必须实现此接口
|
|
1193
|
+
*/
|
|
1194
|
+
class SessionStorageAdapter {
|
|
1195
|
+
/**
|
|
1196
|
+
* 加载会话数据
|
|
1197
|
+
* @param {string} sessionId - 会话 ID
|
|
1198
|
+
* @returns {Promise<Object|null>} 会话数据,不存在返回 null
|
|
1199
|
+
*/
|
|
1200
|
+
async load(sessionId) {
|
|
1201
|
+
throw new Error('Not implemented');
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* 保存会话数据
|
|
1206
|
+
* @param {string} sessionId - 会话 ID
|
|
1207
|
+
* @param {Object} data - 会话数据
|
|
1208
|
+
* @returns {Promise<void>}
|
|
1209
|
+
*/
|
|
1210
|
+
async save(sessionId, data) {
|
|
1211
|
+
throw new Error('Not implemented');
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* 删除会话数据
|
|
1216
|
+
* @param {string} sessionId - 会话 ID
|
|
1217
|
+
* @returns {Promise<void>}
|
|
1218
|
+
*/
|
|
1219
|
+
async delete(sessionId) {
|
|
1220
|
+
throw new Error('Not implemented');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* 列出所有会话 ID
|
|
1225
|
+
* @returns {Promise<string[]>}
|
|
1226
|
+
*/
|
|
1227
|
+
async list() {
|
|
1228
|
+
throw new Error('Not implemented');
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* 检查存储是否健康
|
|
1233
|
+
* @returns {Promise<boolean>}
|
|
1234
|
+
*/
|
|
1235
|
+
async healthCheck() {
|
|
1236
|
+
throw new Error('Not implemented');
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
### 11.4 内置存储实现
|
|
1242
|
+
|
|
1243
|
+
#### 11.4.1 MemoryStorage(默认)
|
|
1244
|
+
|
|
1245
|
+
```javascript
|
|
1246
|
+
/**
|
|
1247
|
+
* 内存存储 - 默认实现
|
|
1248
|
+
* 适用于单进程、无需持久化的场景
|
|
1249
|
+
*/
|
|
1250
|
+
class MemoryStorage extends SessionStorageAdapter {
|
|
1251
|
+
constructor() {
|
|
1252
|
+
super();
|
|
1253
|
+
this._store = new Map(); // sessionId → data
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
async load(sessionId) {
|
|
1257
|
+
return this._store.get(sessionId) || null;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async save(sessionId, data) {
|
|
1261
|
+
this._store.set(sessionId, {
|
|
1262
|
+
...data,
|
|
1263
|
+
_cachedAt: Date.now(),
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async delete(sessionId) {
|
|
1268
|
+
this._store.delete(sessionId);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
async list() {
|
|
1272
|
+
return Array.from(this._store.keys());
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
async healthCheck() {
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
#### 11.4.2 JsonStorage(文件)
|
|
1282
|
+
|
|
1283
|
+
```javascript
|
|
1284
|
+
/**
|
|
1285
|
+
* JSON 文件存储
|
|
1286
|
+
* 适用于需要持久化、数据量较小的场景
|
|
1287
|
+
*/
|
|
1288
|
+
class JsonStorage extends SessionStorageAdapter {
|
|
1289
|
+
/**
|
|
1290
|
+
* @param {Object} options
|
|
1291
|
+
* @param {string} options.dir - 存储目录
|
|
1292
|
+
* @param {string} [options.filePattern='{sessionId}.json'] - 文件名模式
|
|
1293
|
+
*/
|
|
1294
|
+
constructor(options = {}) {
|
|
1295
|
+
super();
|
|
1296
|
+
this.dir = options.dir || '.sessions';
|
|
1297
|
+
this.filePattern = options.filePattern || '{sessionId}.json';
|
|
1298
|
+
this._ensureDir();
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
_getFilePath(sessionId) {
|
|
1302
|
+
const filename = this.filePattern.replace('{sessionId}', sessionId);
|
|
1303
|
+
return path.join(this.dir, filename);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
_ensureDir() {
|
|
1307
|
+
if (!fs.existsSync(this.dir)) {
|
|
1308
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
async load(sessionId) {
|
|
1313
|
+
const filePath = this._getFilePath(sessionId);
|
|
1314
|
+
if (!fs.existsSync(filePath)) {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
1318
|
+
return JSON.parse(content);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
async save(sessionId, data) {
|
|
1322
|
+
const filePath = this._getFilePath(sessionId);
|
|
1323
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
async delete(sessionId) {
|
|
1327
|
+
const filePath = this._getFilePath(sessionId);
|
|
1328
|
+
if (fs.existsSync(filePath)) {
|
|
1329
|
+
await fs.unlink(filePath);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
async list() {
|
|
1334
|
+
if (!fs.existsSync(this.dir)) {
|
|
1335
|
+
return [];
|
|
1336
|
+
}
|
|
1337
|
+
const files = await fs.readdir(this.dir);
|
|
1338
|
+
return files.filter((f) => f.endsWith('.json')).map((f) => f.replace('.json', ''));
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
async healthCheck() {
|
|
1342
|
+
return fs.existsSync(this.dir) && fs.statSync(this.dir).isDirectory();
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
#### 11.4.3 SQLiteStorage(示例)
|
|
1348
|
+
|
|
1349
|
+
```javascript
|
|
1350
|
+
/**
|
|
1351
|
+
* SQLite 数据库存储
|
|
1352
|
+
* 适用于生产环境、需要事务支持的场景
|
|
1353
|
+
*/
|
|
1354
|
+
class SQLiteStorage extends SessionStorageAdapter {
|
|
1355
|
+
/**
|
|
1356
|
+
* @param {Object} options
|
|
1357
|
+
* @param {string} [options.dbPath=':memory:'] - 数据库路径
|
|
1358
|
+
*/
|
|
1359
|
+
constructor(options = {}) {
|
|
1360
|
+
super();
|
|
1361
|
+
this.dbPath = options.dbPath || '.sessions/sessions.db';
|
|
1362
|
+
this._db = null;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async _getDb() {
|
|
1366
|
+
if (!this._db) {
|
|
1367
|
+
const Database = require('better-sqlite3');
|
|
1368
|
+
this._db = new Database(this.dbPath);
|
|
1369
|
+
this._initTable();
|
|
1370
|
+
}
|
|
1371
|
+
return this._db;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
_initTable() {
|
|
1375
|
+
const db = this._db;
|
|
1376
|
+
db.exec(`
|
|
1377
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
1378
|
+
session_id TEXT PRIMARY KEY,
|
|
1379
|
+
data TEXT NOT NULL,
|
|
1380
|
+
created_at INTEGER NOT NULL,
|
|
1381
|
+
updated_at INTEGER NOT NULL
|
|
1382
|
+
)
|
|
1383
|
+
`);
|
|
1384
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_updated_at ON sessions(updated_at)`);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
async load(sessionId) {
|
|
1388
|
+
const db = await this._getDb();
|
|
1389
|
+
const row = db.prepare('SELECT data FROM sessions WHERE session_id = ?').get(sessionId);
|
|
1390
|
+
return row ? JSON.parse(row.data) : null;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
async save(sessionId, data) {
|
|
1394
|
+
const db = await this._getDb();
|
|
1395
|
+
const now = Date.now();
|
|
1396
|
+
const stmt = db.prepare(`
|
|
1397
|
+
INSERT OR REPLACE INTO sessions (session_id, data, created_at, updated_at)
|
|
1398
|
+
VALUES (?, ?, COALESCE((SELECT created_at FROM sessions WHERE session_id = ?), ?), ?)
|
|
1399
|
+
`);
|
|
1400
|
+
stmt.run(sessionId, JSON.stringify(data), sessionId, now, now);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
async delete(sessionId) {
|
|
1404
|
+
const db = await this._getDb();
|
|
1405
|
+
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
async list() {
|
|
1409
|
+
const db = await this._getDb();
|
|
1410
|
+
const rows = db.prepare('SELECT session_id FROM sessions').all();
|
|
1411
|
+
return rows.map((r) => r.session_id);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
async healthCheck() {
|
|
1415
|
+
try {
|
|
1416
|
+
const db = await this._getDb();
|
|
1417
|
+
db.prepare('SELECT 1').get();
|
|
1418
|
+
return true;
|
|
1419
|
+
} catch {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
### 11.5 SessionContext 集成
|
|
1427
|
+
|
|
1428
|
+
```javascript
|
|
1429
|
+
class SessionContext {
|
|
1430
|
+
/**
|
|
1431
|
+
* @param {string} sessionId
|
|
1432
|
+
* @param {Framework} framework
|
|
1433
|
+
* @param {Object} options
|
|
1434
|
+
* @param {SessionStorageAdapter} [options.storage] - 存储适配器,默认 MemoryStorage
|
|
1435
|
+
*/
|
|
1436
|
+
constructor(sessionId, framework, options = {}) {
|
|
1437
|
+
this.sessionId = sessionId;
|
|
1438
|
+
this.framework = framework;
|
|
1439
|
+
this.storage = options.storage || new MemoryStorage();
|
|
1440
|
+
|
|
1441
|
+
// ... 其他初始化
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* 加载会话数据(从存储)
|
|
1446
|
+
* @returns {Promise<boolean>} 是否加载成功
|
|
1447
|
+
*/
|
|
1448
|
+
async loadFromStorage() {
|
|
1449
|
+
const data = await this.storage.load(this.sessionId);
|
|
1450
|
+
if (data) {
|
|
1451
|
+
this.messageStore.messages = data.messages || [];
|
|
1452
|
+
this.messageStore.historyLoaded = true;
|
|
1453
|
+
|
|
1454
|
+
// 恢复 variables
|
|
1455
|
+
if (data.variables) {
|
|
1456
|
+
this.variables = new Map(Object.entries(data.variables));
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// 恢复 metadata
|
|
1460
|
+
if (data.metadata) {
|
|
1461
|
+
this.metadata = { ...this.metadata, ...data.metadata };
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
return true;
|
|
1465
|
+
}
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* 保存会话数据(到存储)
|
|
1471
|
+
* @returns {Promise<void>}
|
|
1472
|
+
*/
|
|
1473
|
+
async saveToStorage() {
|
|
1474
|
+
const data = {
|
|
1475
|
+
sessionId: this.sessionId,
|
|
1476
|
+
messages: this.messageStore.messages,
|
|
1477
|
+
variables: Object.fromEntries(this.variables),
|
|
1478
|
+
metadata: this.metadata,
|
|
1479
|
+
savedAt: Date.now(),
|
|
1480
|
+
};
|
|
1481
|
+
await this.storage.save(this.sessionId, data);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* 删除会话数据
|
|
1486
|
+
* @returns {Promise<void>}
|
|
1487
|
+
*/
|
|
1488
|
+
async deleteFromStorage() {
|
|
1489
|
+
await this.storage.delete(this.sessionId);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
### 11.6 Framework 集成
|
|
1495
|
+
|
|
1496
|
+
```javascript
|
|
1497
|
+
class Framework {
|
|
1498
|
+
constructor(config = {}) {
|
|
1499
|
+
// ...
|
|
1500
|
+
|
|
1501
|
+
// 配置存储适配器
|
|
1502
|
+
const storageConfig = config.storage || {};
|
|
1503
|
+
this._configureStorage(storageConfig);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
_configureStorage(config) {
|
|
1507
|
+
if (typeof config === 'string') {
|
|
1508
|
+
// 简单字符串配置:'memory', 'file', 'json', 'sqlite'
|
|
1509
|
+
switch (config) {
|
|
1510
|
+
case 'memory':
|
|
1511
|
+
this.sessionStorage = new MemoryStorage();
|
|
1512
|
+
break;
|
|
1513
|
+
case 'file':
|
|
1514
|
+
case 'json':
|
|
1515
|
+
this.sessionStorage = new JsonStorage({ dir: '.sessions' });
|
|
1516
|
+
break;
|
|
1517
|
+
case 'sqlite':
|
|
1518
|
+
this.sessionStorage = new SQLiteStorage();
|
|
1519
|
+
break;
|
|
1520
|
+
default:
|
|
1521
|
+
this.sessionStorage = new MemoryStorage();
|
|
1522
|
+
}
|
|
1523
|
+
} else if (config instanceof SessionStorageAdapter) {
|
|
1524
|
+
// 直接传入适配器实例
|
|
1525
|
+
this.sessionStorage = config;
|
|
1526
|
+
} else if (config && typeof config.type === 'string') {
|
|
1527
|
+
// 对象配置 { type: 'file', dir: '.sessions' }
|
|
1528
|
+
switch (config.type) {
|
|
1529
|
+
case 'file':
|
|
1530
|
+
case 'json':
|
|
1531
|
+
this.sessionStorage = new JsonStorage(config);
|
|
1532
|
+
break;
|
|
1533
|
+
case 'sqlite':
|
|
1534
|
+
this.sessionStorage = new SQLiteStorage(config);
|
|
1535
|
+
break;
|
|
1536
|
+
default:
|
|
1537
|
+
this.sessionStorage = new MemoryStorage();
|
|
1538
|
+
}
|
|
1539
|
+
} else {
|
|
1540
|
+
this.sessionStorage = new MemoryStorage();
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* 获取当前使用的存储适配器
|
|
1546
|
+
* @returns {SessionStorageAdapter}
|
|
1547
|
+
*/
|
|
1548
|
+
getSessionStorage() {
|
|
1549
|
+
return this.sessionStorage;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
### 11.7 使用示例
|
|
1555
|
+
|
|
1556
|
+
```javascript
|
|
1557
|
+
// 创建 Framework 时指定存储
|
|
1558
|
+
const framework = await Framework.bootstrap({
|
|
1559
|
+
storage: 'file', // 使用文件存储
|
|
1560
|
+
// 或
|
|
1561
|
+
storage: {
|
|
1562
|
+
type: 'sqlite',
|
|
1563
|
+
dbPath: '.sessions/sessions.db',
|
|
1564
|
+
},
|
|
1565
|
+
// 或
|
|
1566
|
+
storage: new CustomStorageAdapter(), // 自定义存储
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
// 运行时切换存储
|
|
1570
|
+
framework.sessionStorage = new SQLiteStorage({ dbPath: '/tmp/sessions.db' });
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
### 11.8 设计优势
|
|
1574
|
+
|
|
1575
|
+
| 特性 | 说明 |
|
|
1576
|
+
| ------------ | ---------------------------------- |
|
|
1577
|
+
| **接口隔离** | SessionContext 不关心具体存储实现 |
|
|
1578
|
+
| **可测试性** | 使用 MemoryStorage 进行单元测试 |
|
|
1579
|
+
| **渐进迁移** | 可从 JSON 文件迁移到 SQLite |
|
|
1580
|
+
| **插件化** | 用户可实现自己的存储适配器 |
|
|
1581
|
+
| **配置灵活** | 支持字符串、对象、实例多种配置方式 |
|
|
1582
|
+
|
|
1583
|
+
---
|
|
1584
|
+
|
|
1585
|
+
## 附录:关键文件变更清单
|
|
1586
|
+
|
|
1587
|
+
| 文件 | 变更类型 | 描述 |
|
|
1588
|
+
| ----------------------------- | -------- | ------------------------------------------------- |
|
|
1589
|
+
| `src/core/framework.js` | 修改 | 添加三层 AsyncLocalStorage,新增 Context 获取方法 |
|
|
1590
|
+
| `src/core/agent-chat.js` | 修改 | Per-Session 消息存储,移除共享 `_messages` |
|
|
1591
|
+
| `src/core/session-context.js` | 新增 | SessionContext 类 |
|
|
1592
|
+
| `src/core/request-context.js` | 新增 | RequestContext 类 |
|
|
1593
|
+
| `src/core/agent-context.js` | 新增 | AgentContext 类 |
|
|
1594
|
+
| `src/core/context-manager.js` | 新增 | ContextManager 类 |
|
|
1595
|
+
| `plugins/session-plugin.js` | 修改 | 优先使用 SessionContext |
|
|
1596
|
+
| `docs/CONTEXT_DESIGN.md` | 新增 | 本文档 |
|