@gt-fe/eap-runtime 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +3 -2
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +48 -9
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +56 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/common/types.d.ts +2 -2
  8. package/dist/common/types.js +1 -1
  9. package/dist/execution/index.d.ts +2 -0
  10. package/dist/execution/index.js +2 -0
  11. package/dist/execution/interceptors.d.ts +40 -0
  12. package/dist/execution/interceptors.js +23 -0
  13. package/dist/execution/ports.d.ts +18 -0
  14. package/dist/execution/ports.js +13 -0
  15. package/dist/governance/audit-reporter.d.ts +14 -0
  16. package/dist/governance/audit-reporter.js +18 -0
  17. package/dist/governance/index.d.ts +2 -0
  18. package/dist/governance/index.js +1 -0
  19. package/dist/governance/permission-guard.d.ts +6 -2
  20. package/dist/governance/permission-guard.js +38 -5
  21. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  22. package/dist/graph/adapters/graph-nesting.js +29 -0
  23. package/dist/graph/adapters/index.d.ts +2 -0
  24. package/dist/graph/adapters/index.js +2 -0
  25. package/dist/graph/adapters/langgraph.adapter.d.ts +25 -11
  26. package/dist/graph/adapters/langgraph.adapter.js +142 -82
  27. package/dist/graph/adapters/skill-isolate.d.ts +17 -0
  28. package/dist/graph/adapters/skill-isolate.js +50 -0
  29. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  30. package/dist/graph/adapters/subgraph-compiler.js +68 -32
  31. package/dist/graph/contracts/executable-graph.d.ts +27 -5
  32. package/dist/graph/hitl/hitl-handler.js +2 -0
  33. package/dist/graph/nodes/index.d.ts +1 -0
  34. package/dist/graph/nodes/index.js +1 -0
  35. package/dist/graph/nodes/llm-node.d.ts +3 -1
  36. package/dist/graph/nodes/llm-node.js +26 -2
  37. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  38. package/dist/graph/nodes/rag/dedup.js +77 -0
  39. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  40. package/dist/graph/nodes/rag/denoise.js +220 -0
  41. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  42. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  43. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  44. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  45. package/dist/graph/nodes/rag/index.d.ts +18 -0
  46. package/dist/graph/nodes/rag/index.js +17 -0
  47. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  48. package/dist/graph/nodes/rag/prompts.js +40 -0
  49. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  50. package/dist/graph/nodes/rag/rag-node.js +135 -0
  51. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  52. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  53. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  54. package/dist/graph/nodes/rag/react-loop.js +467 -0
  55. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  56. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  57. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  58. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  59. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  60. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  61. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  62. package/dist/graph/nodes/rag/tools.js +260 -0
  63. package/dist/graph/nodes/rag/types.d.ts +226 -0
  64. package/dist/graph/nodes/rag/types.js +9 -0
  65. package/dist/graph/nodes/tool-node.d.ts +19 -1
  66. package/dist/graph/nodes/tool-node.js +162 -33
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +1 -0
  69. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  70. package/dist/memory/semantic-retrieval.service.js +13 -23
  71. package/dist/providers/index.d.ts +1 -1
  72. package/dist/providers/index.js +1 -1
  73. package/dist/providers/routing-llm-provider.d.ts +5 -0
  74. package/dist/providers/routing-llm-provider.js +66 -1
  75. package/dist/runtime/agent-runtime.d.ts +5 -0
  76. package/dist/runtime/agent-runtime.js +86 -66
  77. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  78. package/dist/runtime/dev-rag-deps.js +186 -0
  79. package/dist/runtime/dev-runtime.d.ts +16 -1
  80. package/dist/runtime/dev-runtime.js +59 -5
  81. package/dist/runtime/index.d.ts +1 -1
  82. package/dist/runtime/types.d.ts +10 -9
  83. package/dist/session/apply-session-compression.d.ts +81 -0
  84. package/dist/session/apply-session-compression.js +262 -0
  85. package/dist/session/apply-session-memory.d.ts +21 -0
  86. package/dist/session/apply-session-memory.js +48 -0
  87. package/dist/session/index.d.ts +2 -0
  88. package/dist/session/index.js +2 -0
  89. package/dist/session/persist-turn-messages.d.ts +51 -0
  90. package/dist/session/persist-turn-messages.js +140 -3
  91. package/dist/sse/events/index.d.ts +13 -4
  92. package/dist/sse/events/index.js +9 -1
  93. package/dist/sse/events/rag-events.d.ts +63 -0
  94. package/dist/sse/events/rag-events.js +9 -0
  95. package/dist/sse/events/session-events.d.ts +38 -0
  96. package/dist/sse/events/session-events.js +4 -0
  97. package/dist/token/index.d.ts +2 -1
  98. package/dist/token/index.js +2 -1
  99. package/dist/token/record-task-token-usage.d.ts +2 -0
  100. package/dist/token/record-task-token-usage.js +24 -0
  101. package/dist/token/token-detail-mode.d.ts +4 -4
  102. package/dist/token/token-detail-mode.js +4 -4
  103. package/dist/token/token-detail.d.ts +8 -0
  104. package/dist/token/token-detail.js +22 -0
  105. package/dist/token/token-stats.service.d.ts +2 -2
  106. package/dist/token/token-stats.service.js +1 -1
  107. package/dist/tool/create-tool-executor.d.ts +6 -0
  108. package/dist/tool/create-tool-executor.js +4 -3
  109. package/dist/tool/harness-primitives.d.ts +9 -1
  110. package/dist/tool/harness-primitives.js +43 -12
  111. package/dist/tool/index.d.ts +2 -1
  112. package/dist/tool/index.js +2 -1
  113. package/dist/tool/tool-executor.interface.d.ts +27 -0
  114. package/dist/tool/tool-orchestrator.d.ts +3 -1
  115. package/dist/tool/tool-orchestrator.js +15 -6
  116. package/dist/tool/tool-retry.d.ts +15 -0
  117. package/dist/tool/tool-retry.js +69 -0
  118. package/package.json +2 -2
@@ -29,20 +29,24 @@ import { createLocalLlmProvider, requireLocalLlmConnections } from '../providers
29
29
  import { RoutingLLMProvider } from '../providers/routing-llm-provider.js';
30
30
  import { createToolExecutor } from '../tool/create-tool-executor.js';
31
31
  import { DevToolExecutor } from '../tool/dev-tool-executor.js';
32
- import { withHarnessPrimitives } from '../tool/harness-primitives.js';
32
+ import { captureOverlayJailOptions, withHarnessPrimitives } from '../tool/harness-primitives.js';
33
33
  import { AllowAllGovernanceClient, HttpGovernanceClient, } from '../governance/index.js';
34
34
  import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService, SessionContextService, TaskService, LangGraphAdapter, HitlHandler, TokenBudgetService, TokenStatsService, TokenBudgetGuard, SessionBudgetChecker, createAgentRegistryClient, GraphLoader, } from '../index.js';
35
35
  import { createAgentRuntime } from './agent-runtime.js';
36
36
  import { assembleRuntimeStorage } from './storage-profile.js';
37
+ import { createDevRagNodeDeps } from './dev-rag-deps.js';
38
+ import { RagSearchToolExecutor } from '../graph/nodes/rag/index.js';
37
39
  import { resolveSkillHotReload, watchPreinstallSkillRoot, watchSkillRoots } from '../registry/skill-tree.js';
38
40
  import { RUNTIME_PACKAGE_VERSION } from '../common/runtime-package-version.js';
41
+ import { LongTermMemoryService } from '../memory/long-term-memory.service.js';
42
+ import { isRuntimeMemoryEnabled } from '../session/apply-session-memory.js';
39
43
  /**
40
44
  * 装配本地 dev 模式所需的全部 Runtime 依赖
41
45
  *
42
46
  * @returns DevRuntime(含 invokeOrchestrator / streamOrchestrator / 诊断摘要)
43
47
  * @throws EAP_LLM_MODE=local 但解不出任何密钥时抛错,避免静默 Echo
44
48
  */
45
- export function createDevRuntime() {
49
+ export function createDevRuntime(options) {
46
50
  const foundationUrl = process.env.FOUNDATION_SERVICE_URL ?? 'http://localhost:3100';
47
51
  const foundationConfigured = Boolean(process.env.FOUNDATION_SERVICE_URL && process.env.FOUNDATION_SERVICE_URL.trim().length > 0);
48
52
  const toolExecutionMode = (process.env.EAP_TOOL_EXECUTION_MODE ?? 'mcp').toLowerCase();
@@ -76,12 +80,14 @@ export function createDevRuntime() {
76
80
  llmProviderKind = 'echo';
77
81
  }
78
82
  // ── Tool Executor(dev+mcp 直连,缺配置失败 → 通用路由)──
83
+ // overlay 必须在此处捕获:SDK createAgent 返回后会恢复 process.env。
84
+ const overlayJail = captureOverlayJailOptions();
79
85
  const needsHarnessWrap = toolExecutionMode === 'mcp' && deploymentContext === 'dev';
80
86
  const innerExecutor = toolExecutionMode === 'mcp' && deploymentContext === 'dev'
81
87
  ? new DevToolExecutor()
82
- : createToolExecutor({ foundationUrl, deploymentContext });
88
+ : createToolExecutor({ foundationUrl, deploymentContext, jail: overlayJail });
83
89
  const toolExecutor = needsHarnessWrap
84
- ? withHarnessPrimitives(innerExecutor)
90
+ ? withHarnessPrimitives(innerExecutor, overlayJail)
85
91
  : innerExecutor;
86
92
  // ── Governance ──
87
93
  const governanceClient = process.env.GOVERNANCE_DISABLED === '1' || !foundationConfigured
@@ -92,7 +98,35 @@ export function createDevRuntime() {
92
98
  close: () => { },
93
99
  heartbeat: () => { },
94
100
  });
95
- const graphEngine = new LangGraphAdapter(storage.checkpointSaver, llmProvider, toolExecutor, governanceClient, hitlHandler);
101
+ // rag:EAP_EMBED_API_KEY 显式配置才注入(retriever 惰性初始化,见 dev-rag-deps.ts)
102
+ const ragNodeDeps = createDevRagNodeDeps(llmProvider);
103
+ // 工具形态:RagSearchToolExecutor 装饰工具执行器——拦截 rag_search 跑 rag 内核
104
+ // (多轮检索/融合/降噪,与节点形态同一 runReactLoop),其余工具调用原样透传。
105
+ // EAP_RAG_SOURCES(JSON 数据源描述符数组,工具形态无 rag 节点时的检索面声明位):
106
+ // 会话勾选 ragSources 由此过滤;不配 = 内层工具表不圈定(由请求 loadedTools 决定)
107
+ const ragSourcesEnv = (() => {
108
+ const raw = process.env.EAP_RAG_SOURCES;
109
+ if (!raw)
110
+ return undefined;
111
+ try {
112
+ const parsed = JSON.parse(raw);
113
+ return Array.isArray(parsed) ? { sources: parsed } : undefined;
114
+ }
115
+ catch {
116
+ console.warn('[rag] EAP_RAG_SOURCES 不是合法 JSON 数组,忽略');
117
+ return undefined;
118
+ }
119
+ })();
120
+ const ragToolExecutor = ragNodeDeps
121
+ ? new RagSearchToolExecutor(toolExecutor, ragNodeDeps, {
122
+ config: {
123
+ model: process.env.EAP_DEFAULT_MODEL ?? 'DeepSeek-V4-Flash',
124
+ ...(ragSourcesEnv ? { retrievalSources: ragSourcesEnv } : {}),
125
+ },
126
+ })
127
+ : toolExecutor;
128
+ const graphEngine = new LangGraphAdapter(storage.checkpointSaver, llmProvider, ragToolExecutor, governanceClient, hitlHandler, ragNodeDeps, options?.interceptors);
129
+ graphEngine.bindOverlayExtraRoots(overlayJail.extraRoots);
96
130
  // ── Session / Messages ──
97
131
  const sessionService = new SessionService(storage.sessionRepository);
98
132
  const messageService = new MessageService(storage.messageRepository);
@@ -137,6 +171,19 @@ export function createDevRuntime() {
137
171
  })()
138
172
  : () => { };
139
173
  // ── Orchestrators ──
174
+ const sessionCompression = {
175
+ provider: llmProvider,
176
+ turnLog,
177
+ enabled: options?.compression?.enabled,
178
+ persistCompressed: options?.compression?.persistCompressed,
179
+ };
180
+ const sessionMemory = {
181
+ enabled: options?.memory?.enabled,
182
+ service: options?.memory?.service ??
183
+ (isRuntimeMemoryEnabled(options?.memory?.enabled)
184
+ ? new LongTermMemoryService()
185
+ : undefined),
186
+ };
140
187
  const invokeOrchestrator = new InvokeOrchestrator({
141
188
  sessionContextService,
142
189
  taskService,
@@ -147,6 +194,8 @@ export function createDevRuntime() {
147
194
  messageService,
148
195
  graphLoader,
149
196
  checkpointSaver: storage.checkpointSaver,
197
+ sessionCompression,
198
+ sessionMemory,
150
199
  });
151
200
  const streamOrchestrator = new StreamOrchestrator({
152
201
  sessionContextService,
@@ -158,6 +207,8 @@ export function createDevRuntime() {
158
207
  messageService,
159
208
  graphLoader,
160
209
  checkpointSaver: storage.checkpointSaver,
210
+ sessionCompression,
211
+ sessionMemory,
161
212
  });
162
213
  const agentRuntime = createAgentRuntime({
163
214
  invokeOrchestrator,
@@ -170,6 +221,8 @@ export function createDevRuntime() {
170
221
  messageService,
171
222
  sessionService,
172
223
  turnLog,
224
+ sessionCompression,
225
+ sessionMemory,
173
226
  });
174
227
  return {
175
228
  invokeOrchestrator,
@@ -199,6 +252,7 @@ export function createDevRuntime() {
199
252
  llmTurnKind: storage.diagnostics.llmTurnKind,
200
253
  foundationConfigured,
201
254
  skillHotReload,
255
+ ragEnabled: ragNodeDeps !== undefined,
202
256
  },
203
257
  };
204
258
  }
@@ -1,4 +1,4 @@
1
1
  export { AgentRuntime, createAgentRuntime, type AgentRuntimeDeps, type ForkRelationRecord, type RecordForkRelation, type ConversationDetail, } from './agent-runtime.js';
2
2
  export type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SSEEvent, } from './types.js';
3
- export { createDevRuntime, type DevRuntime, } from './dev-runtime.js';
3
+ export { createDevRuntime, type DevRuntime, type CreateDevRuntimeOptions, } from './dev-runtime.js';
4
4
  export { assembleRuntimeStorage, resolveRuntimeStorageProfile, resolveDevStorageDirs, type RuntimeStorageProfile, type AssembleRuntimeStorageOptions, type AssembledRuntimeStorage, } from './storage-profile.js';
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @see 04-runtime-core/design.md §1.5
8
8
  */
9
- import type { ExecutionContext, InvokeConfig, ResumeHitlResult } from '@gt-fe/eap-contracts';
9
+ import type { ExecutionContext, InvokeConfig, ResumeHitlRequest, ResumeHitlResult } from '@gt-fe/eap-contracts';
10
10
  import type { InvokeResponseData } from '../api/routes/agent.routes.js';
11
11
  import type { GraphMessage } from '../graph/contracts/executable-graph.js';
12
12
  import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
@@ -28,21 +28,22 @@ export interface ExecuteParams {
28
28
  */
29
29
  traceId?: string;
30
30
  config?: InvokeConfig;
31
+ /** 请求级检索范围(前端圈定已向量化文件/目录;rag 节点本地检索源生效) */
32
+ ragScope?: {
33
+ fileIds?: string[];
34
+ dirPaths?: string[];
35
+ };
36
+ /** 会话级数据源勾选(数据源 id 列表;缺省 = 预定义目录全部启用) */
37
+ ragSources?: string[];
31
38
  }
32
39
  /** 同步执行结果 */
33
40
  export type ExecuteResult = InvokeResponseData;
34
41
  /** HITL 恢复结果(与 @gt-fe/eap-contracts ResumeHitlResult 对齐) */
35
42
  export type { ResumeHitlResult };
36
- /** HITL 用户响应 */
37
- export interface HitlResponse {
43
+ /** HITL 用户响应(与 @gt-fe/eap-contracts ResumeHitlRequest 对齐;userId 必填) */
44
+ export interface HitlResponse extends ResumeHitlRequest {
38
45
  choice: string;
39
46
  userId: string;
40
- executionContext?: ExecutionContext;
41
- /**
42
- * Checkpoint thread_id 提示(冷恢复)。
43
- * Hosting 可从 tasks.output.hitlThreadId 注入;优先于 sessionId。
44
- */
45
- threadId?: string;
46
47
  }
47
48
  /** Fork 入参:上层先发 taskId / traceId,Runtime 不再 createFlow */
48
49
  export interface ForkParams {
@@ -0,0 +1,81 @@
1
+ /**
2
+ * execute/stream 装载历史后的压缩入口。默认关。
3
+ *
4
+ * C-1a 开关:options.enabled 或 EAP_RUNTIME_COMPRESSION=1
5
+ * C-1b 有窗口:用上一轮 llm_turns 的 used/window,不用字符估窗
6
+ * C-1c 无窗口:只按轮次上限,不按窗压
7
+ * C-1d 摘要只走 ILLMProvider;失败则不压缩
8
+ * C-1e 默认只改本轮内存历史;persistCompressed 时必须能写 compression record
9
+ * C-1f 流式路径在摘要前产出 session.compression.started,结束后产出 completed。
10
+ * 不新开「占用 ≥ 80% 再 compact」链;触发仍是 C-1b/c。
11
+ */
12
+ import type { GraphMessage } from '../graph/contracts/executable-graph.js';
13
+ import type { ILLMProvider } from '../graph/nodes/llm-node.js';
14
+ import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
15
+ import { type CompressionRecord } from '../memory/message-compression.service.js';
16
+ import type { ICompressionRepository } from '../memory/compression/compression.repository.js';
17
+ import type { SessionCompressionCompletedPayload, SessionCompressionReason, SessionCompressionStartedPayload } from '../sse/events/session-events.js';
18
+ export interface SessionCompressionOptions {
19
+ enabled?: boolean;
20
+ persistCompressed?: boolean;
21
+ provider?: ILLMProvider;
22
+ turnLog?: ILlmTurnLog;
23
+ compressionRepository?: ICompressionRepository;
24
+ maxRounds?: number;
25
+ preserveRecentRounds?: number;
26
+ }
27
+ export interface RunSessionCompressionParams {
28
+ history: GraphMessage[];
29
+ sessionId: string;
30
+ userId?: string;
31
+ taskId?: string;
32
+ options?: SessionCompressionOptions;
33
+ env?: NodeJS.ProcessEnv;
34
+ }
35
+ export interface RunSessionCompressionResult {
36
+ messages: GraphMessage[];
37
+ compressed: boolean;
38
+ record?: CompressionRecord;
39
+ completed?: SessionCompressionCompletedPayload;
40
+ }
41
+ export interface SessionCompressionDecision {
42
+ needed: boolean;
43
+ originalMessageCount: number;
44
+ reason?: SessionCompressionReason;
45
+ used?: number;
46
+ window?: number;
47
+ percent?: number;
48
+ threshold?: number;
49
+ started?: SessionCompressionStartedPayload;
50
+ }
51
+ export type SessionCompressionStep = {
52
+ kind: 'started';
53
+ payload: SessionCompressionStartedPayload;
54
+ } | {
55
+ kind: 'completed';
56
+ payload: SessionCompressionCompletedPayload;
57
+ } | {
58
+ kind: 'history';
59
+ messages: GraphMessage[];
60
+ };
61
+ export declare function isRuntimeCompressionEnabled(explicit?: boolean, env?: NodeJS.ProcessEnv): boolean;
62
+ export declare function isRuntimeCompressionPersist(explicit?: boolean, env?: NodeJS.ProcessEnv): boolean;
63
+ export declare function formatCompressionStartedMessage(input: {
64
+ percent?: number;
65
+ reason: SessionCompressionReason;
66
+ threshold?: number;
67
+ }): string;
68
+ export declare function formatCompressionCompletedMessage(input: {
69
+ success: boolean;
70
+ originalMessageCount: number;
71
+ compressedMessageCount: number;
72
+ error?: string;
73
+ }): string;
74
+ export declare function applySessionCompression(params: RunSessionCompressionParams): Promise<GraphMessage[]>;
75
+ /**
76
+ * 流式入口用:先产出 started,再跑摘要,再产出 completed,最后给出本轮历史。
77
+ * SDK `stream` 可在 started 之后才继续 next(),HTTP SSE 可立刻 emit。
78
+ */
79
+ export declare function iterSessionCompression(params: RunSessionCompressionParams): AsyncGenerator<SessionCompressionStep>;
80
+ export declare function runSessionCompression(params: RunSessionCompressionParams): Promise<RunSessionCompressionResult>;
81
+ export declare function decideSessionCompression(params: RunSessionCompressionParams): Promise<SessionCompressionDecision>;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * execute/stream 装载历史后的压缩入口。默认关。
3
+ *
4
+ * C-1a 开关:options.enabled 或 EAP_RUNTIME_COMPRESSION=1
5
+ * C-1b 有窗口:用上一轮 llm_turns 的 used/window,不用字符估窗
6
+ * C-1c 无窗口:只按轮次上限,不按窗压
7
+ * C-1d 摘要只走 ILLMProvider;失败则不压缩
8
+ * C-1e 默认只改本轮内存历史;persistCompressed 时必须能写 compression record
9
+ * C-1f 流式路径在摘要前产出 session.compression.started,结束后产出 completed。
10
+ * 不新开「占用 ≥ 80% 再 compact」链;触发仍是 C-1b/c。
11
+ */
12
+ import { EchoLLMProvider } from '../providers/echo-llm-provider.js';
13
+ import { COMPRESSION_WINDOW_FACTOR, countRounds, DEFAULT_COMPRESSION_CONFIG, } from '../memory/message-compression.service.js';
14
+ import { SlidingWindowCompression } from '../memory/compression/sliding-window.compression.js';
15
+ import { emitRuntimeLog } from '../common/logger.js';
16
+ export function isRuntimeCompressionEnabled(explicit, env = process.env) {
17
+ if (explicit === true)
18
+ return true;
19
+ if (explicit === false)
20
+ return false;
21
+ const raw = env.EAP_RUNTIME_COMPRESSION ?? env.EAP_COMPRESSION_ENABLED;
22
+ return raw === '1' || raw === 'true';
23
+ }
24
+ export function isRuntimeCompressionPersist(explicit, env = process.env) {
25
+ if (explicit === true)
26
+ return true;
27
+ if (explicit === false)
28
+ return false;
29
+ return env.EAP_RUNTIME_COMPRESSION_PERSIST === '1' || env.EAP_RUNTIME_COMPRESSION_PERSIST === 'true';
30
+ }
31
+ export function formatCompressionStartedMessage(input) {
32
+ if (typeof input.percent === 'number' && Number.isFinite(input.percent)) {
33
+ return `Context ${input.percent}% full. Compacting...`;
34
+ }
35
+ if (typeof input.threshold === 'number' && Number.isFinite(input.threshold)) {
36
+ return `Conversation exceeded ${input.threshold} rounds. Compacting...`;
37
+ }
38
+ return 'Compacting conversation history...';
39
+ }
40
+ export function formatCompressionCompletedMessage(input) {
41
+ if (!input.success) {
42
+ return input.error
43
+ ? 'Compression failed; using original history.'
44
+ : 'Compression skipped; using original history.';
45
+ }
46
+ return `Compressed ${input.originalMessageCount} → ${input.compressedMessageCount} messages.`;
47
+ }
48
+ export async function applySessionCompression(params) {
49
+ const result = await runSessionCompression(params);
50
+ return result.messages;
51
+ }
52
+ /**
53
+ * 流式入口用:先产出 started,再跑摘要,再产出 completed,最后给出本轮历史。
54
+ * SDK `stream` 可在 started 之后才继续 next(),HTTP SSE 可立刻 emit。
55
+ */
56
+ export async function* iterSessionCompression(params) {
57
+ const history = params.history ?? [];
58
+ const decision = await decideSessionCompression(params);
59
+ if (!decision.needed || !decision.started) {
60
+ yield { kind: 'history', messages: history };
61
+ return;
62
+ }
63
+ yield { kind: 'started', payload: decision.started };
64
+ const result = await runDecidedSessionCompression(params, decision);
65
+ if (result.completed) {
66
+ yield { kind: 'completed', payload: result.completed };
67
+ }
68
+ yield { kind: 'history', messages: result.messages };
69
+ }
70
+ export async function runSessionCompression(params) {
71
+ const decision = await decideSessionCompression(params);
72
+ if (!decision.needed) {
73
+ return { messages: params.history ?? [], compressed: false };
74
+ }
75
+ return runDecidedSessionCompression(params, decision);
76
+ }
77
+ export async function decideSessionCompression(params) {
78
+ const history = params.history ?? [];
79
+ const originalMessageCount = history.length;
80
+ const options = params.options ?? {};
81
+ const env = params.env ?? process.env;
82
+ const enabled = isRuntimeCompressionEnabled(options.enabled, env);
83
+ if (!enabled || history.length === 0) {
84
+ return { needed: false, originalMessageCount };
85
+ }
86
+ const provider = options.provider;
87
+ if (!provider || provider instanceof EchoLLMProvider) {
88
+ return { needed: false, originalMessageCount };
89
+ }
90
+ const maxRounds = options.maxRounds ?? DEFAULT_COMPRESSION_CONFIG.maxRounds;
91
+ const last = await lastTurnUsage(options.turnLog, params.sessionId);
92
+ const needed = last?.window
93
+ ? last.used >= Math.floor(last.window * COMPRESSION_WINDOW_FACTOR)
94
+ : countRounds(history) > maxRounds;
95
+ if (!needed) {
96
+ return { needed: false, originalMessageCount };
97
+ }
98
+ const reason = last?.window ? 'window' : 'max_rounds';
99
+ const percent = last?.window && last.window > 0 ? Math.round((last.used / last.window) * 100) : undefined;
100
+ const threshold = last?.window
101
+ ? Math.floor(last.window * COMPRESSION_WINDOW_FACTOR)
102
+ : maxRounds;
103
+ const started = {
104
+ sessionId: params.sessionId,
105
+ ...(params.taskId ? { taskId: params.taskId } : {}),
106
+ reason,
107
+ originalMessageCount,
108
+ ...(last ? { used: last.used } : {}),
109
+ ...(last?.window ? { window: last.window } : {}),
110
+ ...(percent !== undefined ? { percent } : {}),
111
+ threshold,
112
+ displayMessage: formatCompressionStartedMessage({ percent, reason, threshold }),
113
+ };
114
+ return {
115
+ needed: true,
116
+ originalMessageCount,
117
+ reason,
118
+ used: last?.used,
119
+ window: last?.window,
120
+ percent,
121
+ threshold,
122
+ started,
123
+ };
124
+ }
125
+ async function runDecidedSessionCompression(params, decision) {
126
+ const history = params.history ?? [];
127
+ const options = params.options ?? {};
128
+ const env = params.env ?? process.env;
129
+ const provider = options.provider;
130
+ if (!provider || provider instanceof EchoLLMProvider || !decision.needed) {
131
+ return {
132
+ messages: history,
133
+ compressed: false,
134
+ completed: completedPayload(params, decision, {
135
+ success: false,
136
+ compressedMessageCount: history.length,
137
+ persisted: false,
138
+ }),
139
+ };
140
+ }
141
+ const persist = isRuntimeCompressionPersist(options.persistCompressed, env);
142
+ if (persist && !options.compressionRepository) {
143
+ emitRuntimeLog('warn', 'runtime.compression', 'persist requested without repository; memory only', {
144
+ event: 'runtime.compression.persist_skipped',
145
+ sessionId: params.sessionId,
146
+ });
147
+ }
148
+ const maxRounds = options.maxRounds ?? DEFAULT_COMPRESSION_CONFIG.maxRounds;
149
+ try {
150
+ const compression = new SlidingWindowCompression({
151
+ maxRounds,
152
+ preserveRecentRounds: options.preserveRecentRounds ?? DEFAULT_COMPRESSION_CONFIG.preserveRecentRounds,
153
+ ...(decision.window ? { maxContextTokens: decision.window } : {}),
154
+ }, async (msgs) => summarizeWithProvider(provider, msgs));
155
+ const compressed = await compression.compress(history);
156
+ if (compressed === history) {
157
+ return {
158
+ messages: history,
159
+ compressed: false,
160
+ completed: completedPayload(params, decision, {
161
+ success: false,
162
+ compressedMessageCount: history.length,
163
+ persisted: false,
164
+ }),
165
+ };
166
+ }
167
+ const record = {
168
+ sessionId: params.sessionId,
169
+ taskId: params.taskId ?? null,
170
+ originalMessageCount: history.length,
171
+ compressedMessageCount: compressed.length,
172
+ originalTokens: decision.used ?? 0,
173
+ compressedTokens: 0,
174
+ strategy: 'sliding-window',
175
+ summaryContent: compressed[0]?.content ?? '',
176
+ createdBy: params.userId ?? 'system',
177
+ createdAt: new Date().toISOString(),
178
+ };
179
+ let persisted = false;
180
+ if (persist && options.compressionRepository) {
181
+ await options.compressionRepository.save(record);
182
+ persisted = true;
183
+ }
184
+ return {
185
+ messages: compressed,
186
+ compressed: true,
187
+ record,
188
+ completed: completedPayload(params, decision, {
189
+ success: true,
190
+ compressedMessageCount: compressed.length,
191
+ persisted,
192
+ }),
193
+ };
194
+ }
195
+ catch (err) {
196
+ const error = err instanceof Error ? err.message : String(err);
197
+ emitRuntimeLog('warn', 'runtime.compression', 'compress failed; using original history', {
198
+ event: 'runtime.compression.failed',
199
+ sessionId: params.sessionId,
200
+ error,
201
+ });
202
+ return {
203
+ messages: history,
204
+ compressed: false,
205
+ completed: completedPayload(params, decision, {
206
+ success: false,
207
+ compressedMessageCount: history.length,
208
+ persisted: false,
209
+ error,
210
+ }),
211
+ };
212
+ }
213
+ }
214
+ function completedPayload(params, decision, outcome) {
215
+ const reason = decision.reason ?? 'max_rounds';
216
+ return {
217
+ sessionId: params.sessionId,
218
+ ...(params.taskId ? { taskId: params.taskId } : {}),
219
+ reason,
220
+ success: outcome.success,
221
+ originalMessageCount: decision.originalMessageCount,
222
+ compressedMessageCount: outcome.compressedMessageCount,
223
+ ...(decision.used !== undefined ? { used: decision.used } : {}),
224
+ ...(decision.window !== undefined ? { window: decision.window } : {}),
225
+ ...(decision.percent !== undefined ? { percent: decision.percent } : {}),
226
+ persisted: outcome.persisted,
227
+ ...(outcome.error ? { error: outcome.error } : {}),
228
+ displayMessage: formatCompressionCompletedMessage({
229
+ success: outcome.success,
230
+ originalMessageCount: decision.originalMessageCount,
231
+ compressedMessageCount: outcome.compressedMessageCount,
232
+ error: outcome.error,
233
+ }),
234
+ };
235
+ }
236
+ async function lastTurnUsage(turnLog, sessionId) {
237
+ if (!turnLog || !sessionId)
238
+ return undefined;
239
+ const index = await turnLog.list(sessionId);
240
+ const last = index[index.length - 1];
241
+ if (!last || typeof last.used !== 'number')
242
+ return undefined;
243
+ return { used: last.used, window: last.window };
244
+ }
245
+ async function summarizeWithProvider(provider, messages) {
246
+ const body = messages
247
+ .map((m) => `${m.role}: ${m.content}`)
248
+ .join('\n')
249
+ .slice(0, 12000);
250
+ const response = await provider.invoke([
251
+ {
252
+ role: 'system',
253
+ content: '将对话历史压成短摘要,保留用户需求、结论和未完成事项。',
254
+ },
255
+ { role: 'user', content: body },
256
+ ], { model: 'default', temperature: 0.2, maxTokens: 1024 });
257
+ const text = response.content?.trim();
258
+ if (!text) {
259
+ throw new Error('empty compression summary');
260
+ }
261
+ return text;
262
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * execute/stream 装载历史后注入长期记忆。默认关。
3
+ * 只用关键词匹配;未注入真实 embedding 不做向量检索。
4
+ */
5
+ import type { GraphMessage } from '../graph/contracts/executable-graph.js';
6
+ import { LongTermMemoryService, type MemoryRecord } from '../memory/long-term-memory.service.js';
7
+ export interface SessionMemoryOptions {
8
+ enabled?: boolean;
9
+ service?: LongTermMemoryService;
10
+ limit?: number;
11
+ }
12
+ export declare function isRuntimeMemoryEnabled(explicit?: boolean, env?: NodeJS.ProcessEnv): boolean;
13
+ export declare function applySessionMemory(params: {
14
+ history: GraphMessage[];
15
+ userId?: string;
16
+ agentCode?: string;
17
+ query?: string;
18
+ options?: SessionMemoryOptions;
19
+ env?: NodeJS.ProcessEnv;
20
+ }): Promise<GraphMessage[]>;
21
+ export declare function injectMemoryHits(history: GraphMessage[], memories: MemoryRecord[]): GraphMessage[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * execute/stream 装载历史后注入长期记忆。默认关。
3
+ * 只用关键词匹配;未注入真实 embedding 不做向量检索。
4
+ */
5
+ import { SemanticRetrievalService } from '../memory/semantic-retrieval.service.js';
6
+ export function isRuntimeMemoryEnabled(explicit, env = process.env) {
7
+ if (explicit === true)
8
+ return true;
9
+ if (explicit === false)
10
+ return false;
11
+ const raw = env.EAP_RUNTIME_MEMORY;
12
+ return raw === '1' || raw === 'true';
13
+ }
14
+ export async function applySessionMemory(params) {
15
+ const history = params.history ?? [];
16
+ const options = params.options ?? {};
17
+ const env = params.env ?? process.env;
18
+ if (!isRuntimeMemoryEnabled(options.enabled, env))
19
+ return history;
20
+ const service = options.service;
21
+ if (!service || !params.userId || !params.agentCode)
22
+ return history;
23
+ const query = params.query?.trim() ||
24
+ [...history].reverse().find((m) => m.role === 'user')?.content?.trim() ||
25
+ '';
26
+ if (!query)
27
+ return history;
28
+ const stored = await service.retrieve(params.userId, params.agentCode, 50);
29
+ if (stored.length === 0)
30
+ return history;
31
+ const retrieval = new SemanticRetrievalService({
32
+ strategy: 'keyword',
33
+ limit: options.limit ?? 10,
34
+ });
35
+ const hits = await retrieval.retrieve(query, stored);
36
+ if (hits.length === 0)
37
+ return history;
38
+ return injectMemoryHits(history, hits.map((h) => h.record));
39
+ }
40
+ export function injectMemoryHits(history, memories) {
41
+ if (memories.length === 0)
42
+ return history;
43
+ const memoryContent = memories.map((m) => `[${m.type}] ${m.content}`).join('\n');
44
+ return [
45
+ { role: 'system', content: `[Long-term Memory]\n${memoryContent}` },
46
+ ...history,
47
+ ];
48
+ }
@@ -10,4 +10,6 @@ export type { FileMessageRepositoryOptions } from './file-message.repository.js'
10
10
  export { SessionContextService } from './session-context.service.js';
11
11
  export type { ResolveSessionParams, SessionContext, } from './session-context.service.js';
12
12
  export { CompressionMiddleware, createCompressionMiddleware } from './compression-middleware.js';
13
+ export { applySessionCompression, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, type SessionCompressionOptions, type RunSessionCompressionParams, type RunSessionCompressionResult, type SessionCompressionDecision, type SessionCompressionStep, } from './apply-session-compression.js';
13
14
  export type { CompressionMiddlewareConfig } from './compression-middleware.js';
15
+ export { applySessionMemory, isRuntimeMemoryEnabled, type SessionMemoryOptions, } from './apply-session-memory.js';
@@ -10,3 +10,5 @@ export { FileMessageRepository } from './file-message.repository.js';
10
10
  export { SessionContextService } from './session-context.service.js';
11
11
  // 压缩中间件
12
12
  export { CompressionMiddleware, createCompressionMiddleware } from './compression-middleware.js';
13
+ export { applySessionCompression, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, } from './apply-session-compression.js';
14
+ export { applySessionMemory, isRuntimeMemoryEnabled, } from './apply-session-memory.js';