@poolzin/pool-bot 2026.3.6 → 2026.3.9
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/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/skills/security.js +217 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
package/dist/cli/tagline.js
CHANGED
|
@@ -191,6 +191,13 @@ export function activeTaglines(options = {}) {
|
|
|
191
191
|
return filtered.length > 0 ? filtered : TAGLINES;
|
|
192
192
|
}
|
|
193
193
|
export function pickTagline(options = {}) {
|
|
194
|
+
const mode = options.mode;
|
|
195
|
+
if (mode === "off") {
|
|
196
|
+
return "";
|
|
197
|
+
}
|
|
198
|
+
if (mode === "default") {
|
|
199
|
+
return DEFAULT_TAGLINE;
|
|
200
|
+
}
|
|
194
201
|
const env = options.env ?? process.env;
|
|
195
202
|
const override = env?.POOLBOT_TAGLINE_INDEX ?? env?.CLAWDBOT_TAGLINE_INDEX;
|
|
196
203
|
if (override !== undefined) {
|
package/dist/config/config.js
CHANGED
|
@@ -2,6 +2,7 @@ export { createConfigIO, loadConfig, parseConfigJson5, readConfigFileSnapshot, r
|
|
|
2
2
|
export { migrateLegacyConfig } from "./legacy-migrate.js";
|
|
3
3
|
export * from "./paths.js";
|
|
4
4
|
export * from "./runtime-overrides.js";
|
|
5
|
+
export * from "./secrets-integration.js";
|
|
5
6
|
export * from "./types.js";
|
|
6
7
|
export { validateConfigObject, validateConfigObjectWithPlugins } from "./validation.js";
|
|
7
8
|
export { PoolBotSchema } from "./zod-schema.js";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Secrets Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates the secrets management system with config loading.
|
|
5
|
+
* Resolves SecretRefs in config values at runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { SecretsRuntime } from "../secrets/index.js";
|
|
8
|
+
const SECRET_PATHS = [
|
|
9
|
+
"agent.model",
|
|
10
|
+
"agent.apiKey",
|
|
11
|
+
"channels.telegram.botToken",
|
|
12
|
+
"channels.discord.token",
|
|
13
|
+
"channels.slack.botToken",
|
|
14
|
+
"channels.slack.appToken",
|
|
15
|
+
"gateway.auth.token",
|
|
16
|
+
"gateway.auth.password",
|
|
17
|
+
"memory.apiKey",
|
|
18
|
+
"tools.browser.apiKey",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Deeply traverse config and resolve secret references
|
|
22
|
+
*/
|
|
23
|
+
export function resolveConfigSecrets(config, secretsRuntime) {
|
|
24
|
+
const resolved = { ...config };
|
|
25
|
+
// Resolve specific known secret paths
|
|
26
|
+
for (const path of SECRET_PATHS) {
|
|
27
|
+
resolvePath(resolved, path.split("."), secretsRuntime);
|
|
28
|
+
}
|
|
29
|
+
// Also scan for any string values that look like secret refs
|
|
30
|
+
scanAndResolveSecrets(resolved, "", secretsRuntime);
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
function resolvePath(obj, path, runtime) {
|
|
34
|
+
if (path.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const [head, ...tail] = path;
|
|
37
|
+
if (!(head in obj))
|
|
38
|
+
return;
|
|
39
|
+
if (tail.length === 0) {
|
|
40
|
+
// Leaf node - resolve if it's a string
|
|
41
|
+
const value = obj[head];
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
const result = runtime.resolve(head, value);
|
|
44
|
+
if (result.value !== undefined) {
|
|
45
|
+
obj[head] = result.value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Traverse deeper
|
|
51
|
+
const child = obj[head];
|
|
52
|
+
if (typeof child === "object" && child !== null && !Array.isArray(child)) {
|
|
53
|
+
resolvePath(child, tail, runtime);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function scanAndResolveSecrets(obj, path, runtime) {
|
|
58
|
+
if (typeof obj === "string") {
|
|
59
|
+
// Check if it looks like a secret ref
|
|
60
|
+
if (obj.startsWith("env:") || obj.startsWith("file:")) {
|
|
61
|
+
runtime.resolve(path || "unknown", obj);
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(obj)) {
|
|
66
|
+
obj.forEach((item, index) => {
|
|
67
|
+
scanAndResolveSecrets(item, `${path}[${index}]`, runtime);
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (typeof obj === "object" && obj !== null) {
|
|
72
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
73
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
74
|
+
scanAndResolveSecrets(value, newPath, runtime);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a secrets runtime from config
|
|
80
|
+
*/
|
|
81
|
+
export function createSecretsRuntimeFromConfig(_config, secretsConfig) {
|
|
82
|
+
return new SecretsRuntime({
|
|
83
|
+
allowDirectValues: true,
|
|
84
|
+
warnOnDirectValues: true,
|
|
85
|
+
failOnUnresolved: false,
|
|
86
|
+
...secretsConfig,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Default permissive capabilities (when security is disabled). */
|
|
2
|
+
export const PERMISSIVE_CAPABILITIES = [
|
|
3
|
+
{ type: "file:read", pattern: "*" },
|
|
4
|
+
{ type: "file:write", pattern: "*" },
|
|
5
|
+
{ type: "net:connect", pattern: "*" },
|
|
6
|
+
{ type: "tool:all" },
|
|
7
|
+
{ type: "llm:query", pattern: "*" },
|
|
8
|
+
{ type: "llm:maxTokens", limit: Number.MAX_SAFE_INTEGER },
|
|
9
|
+
{ type: "agent:spawn" },
|
|
10
|
+
{ type: "agent:message", pattern: "*" },
|
|
11
|
+
{ type: "agent:kill", pattern: "*" },
|
|
12
|
+
{ type: "memory:read", scope: "*" },
|
|
13
|
+
{ type: "memory:write", scope: "*" },
|
|
14
|
+
{ type: "shell:exec", pattern: "*" },
|
|
15
|
+
{ type: "env:read", pattern: "*" },
|
|
16
|
+
{ type: "gateway:admin" },
|
|
17
|
+
{ type: "gateway:channels:read" },
|
|
18
|
+
{ type: "gateway:channels:write", pattern: "*" },
|
|
19
|
+
{ type: "econ:spend", limit: Number.MAX_SAFE_INTEGER },
|
|
20
|
+
{ type: "econ:earn" },
|
|
21
|
+
{ type: "econ:transfer", pattern: "*" },
|
|
22
|
+
];
|
|
23
|
+
/** Restricted capabilities for untrusted agents. */
|
|
24
|
+
export const RESTRICTED_CAPABILITIES = [
|
|
25
|
+
{ type: "file:read", pattern: "/data/*" },
|
|
26
|
+
{ type: "net:connect", pattern: "*.openai.com:443" },
|
|
27
|
+
{ type: "tool:invoke", toolId: "web_search" },
|
|
28
|
+
{ type: "tool:invoke", toolId: "file_read" },
|
|
29
|
+
{ type: "llm:query", pattern: "gpt-4*" },
|
|
30
|
+
{ type: "llm:maxTokens", limit: 10000 },
|
|
31
|
+
{ type: "memory:read", scope: "session/*" },
|
|
32
|
+
{ type: "memory:write", scope: "session/*" },
|
|
33
|
+
];
|
|
@@ -6,6 +6,7 @@ import { HexColorSchema, ModelsConfigSchema } from "./zod-schema.core.js";
|
|
|
6
6
|
import { HookMappingSchema, HooksGmailSchema, InternalHooksSchema } from "./zod-schema.hooks.js";
|
|
7
7
|
import { InstallRecordShape } from "./zod-schema.installs.js";
|
|
8
8
|
import { ChannelsSchema } from "./zod-schema.providers.js";
|
|
9
|
+
import { SecuritySchema } from "./zod-schema.security.js";
|
|
9
10
|
import { sensitive } from "./zod-schema.sensitive.js";
|
|
10
11
|
import { CommandsSchema, MessagesSchema, SessionSchema, SessionSendPolicySchema, } from "./zod-schema.session.js";
|
|
11
12
|
const BrowserSnapshotDefaultsSchema = z
|
|
@@ -641,6 +642,20 @@ export const PoolBotSchema = z
|
|
|
641
642
|
})
|
|
642
643
|
.strict()
|
|
643
644
|
.optional(),
|
|
645
|
+
cli: z
|
|
646
|
+
.object({
|
|
647
|
+
banner: z
|
|
648
|
+
.object({
|
|
649
|
+
taglineMode: z
|
|
650
|
+
.union([z.literal("random"), z.literal("default"), z.literal("off")])
|
|
651
|
+
.optional(),
|
|
652
|
+
})
|
|
653
|
+
.strict()
|
|
654
|
+
.optional(),
|
|
655
|
+
})
|
|
656
|
+
.strict()
|
|
657
|
+
.optional(),
|
|
658
|
+
security: SecuritySchema,
|
|
644
659
|
})
|
|
645
660
|
.strict()
|
|
646
661
|
.superRefine((cfg, ctx) => {
|
|
@@ -583,6 +583,7 @@ export const SlackAccountSchema = z
|
|
|
583
583
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
584
584
|
responsePrefix: z.string().optional(),
|
|
585
585
|
ackReaction: z.string().optional(),
|
|
586
|
+
typingReaction: z.string().optional(),
|
|
586
587
|
})
|
|
587
588
|
.strict()
|
|
588
589
|
.superRefine((value, ctx) => {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for security configuration.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/** Capability type schema - matches the TypeScript Capability type. */
|
|
6
|
+
const CapabilitySchema = z.union([
|
|
7
|
+
// File capabilities
|
|
8
|
+
z.object({
|
|
9
|
+
type: z.literal("file:read"),
|
|
10
|
+
pattern: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
z.object({
|
|
13
|
+
type: z.literal("file:write"),
|
|
14
|
+
pattern: z.string(),
|
|
15
|
+
}),
|
|
16
|
+
// Network capabilities
|
|
17
|
+
z.object({
|
|
18
|
+
type: z.literal("net:connect"),
|
|
19
|
+
pattern: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
// Tool capabilities
|
|
22
|
+
z.object({
|
|
23
|
+
type: z.literal("tool:invoke"),
|
|
24
|
+
toolId: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("tool:all"),
|
|
28
|
+
}),
|
|
29
|
+
// LLM capabilities
|
|
30
|
+
z.object({
|
|
31
|
+
type: z.literal("llm:query"),
|
|
32
|
+
pattern: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("llm:maxTokens"),
|
|
36
|
+
limit: z.number().int().positive(),
|
|
37
|
+
}),
|
|
38
|
+
// Agent capabilities
|
|
39
|
+
z.object({
|
|
40
|
+
type: z.literal("agent:spawn"),
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
type: z.literal("agent:message"),
|
|
44
|
+
pattern: z.string(),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("agent:kill"),
|
|
48
|
+
pattern: z.string(),
|
|
49
|
+
}),
|
|
50
|
+
// Memory capabilities
|
|
51
|
+
z.object({
|
|
52
|
+
type: z.literal("memory:read"),
|
|
53
|
+
scope: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
z.object({
|
|
56
|
+
type: z.literal("memory:write"),
|
|
57
|
+
scope: z.string(),
|
|
58
|
+
}),
|
|
59
|
+
// Shell capabilities
|
|
60
|
+
z.object({
|
|
61
|
+
type: z.literal("shell:exec"),
|
|
62
|
+
pattern: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
// Environment capabilities
|
|
65
|
+
z.object({
|
|
66
|
+
type: z.literal("env:read"),
|
|
67
|
+
pattern: z.string(),
|
|
68
|
+
}),
|
|
69
|
+
// Gateway capabilities
|
|
70
|
+
z.object({
|
|
71
|
+
type: z.literal("gateway:admin"),
|
|
72
|
+
}),
|
|
73
|
+
z.object({
|
|
74
|
+
type: z.literal("gateway:channels:read"),
|
|
75
|
+
}),
|
|
76
|
+
z.object({
|
|
77
|
+
type: z.literal("gateway:channels:write"),
|
|
78
|
+
pattern: z.string(),
|
|
79
|
+
}),
|
|
80
|
+
// Economic capabilities
|
|
81
|
+
z.object({
|
|
82
|
+
type: z.literal("econ:spend"),
|
|
83
|
+
limit: z.number().int().nonnegative(),
|
|
84
|
+
}),
|
|
85
|
+
z.object({
|
|
86
|
+
type: z.literal("econ:earn"),
|
|
87
|
+
}),
|
|
88
|
+
z.object({
|
|
89
|
+
type: z.literal("econ:transfer"),
|
|
90
|
+
pattern: z.string(),
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
93
|
+
/** Agent capability configuration schema. */
|
|
94
|
+
const AgentCapabilityConfigSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
agentId: z.string(),
|
|
97
|
+
capabilities: z.array(CapabilitySchema),
|
|
98
|
+
})
|
|
99
|
+
.strict();
|
|
100
|
+
/** Security configuration schema. */
|
|
101
|
+
export const SecuritySchema = z
|
|
102
|
+
.object({
|
|
103
|
+
/** Enable capability enforcement. Default: false (permissive mode). */
|
|
104
|
+
enabled: z.boolean().optional(),
|
|
105
|
+
/** Default capabilities for agents without explicit config. */
|
|
106
|
+
defaultCapabilities: z.array(CapabilitySchema).optional(),
|
|
107
|
+
/** Per-agent capability configurations. */
|
|
108
|
+
agents: z.array(AgentCapabilityConfigSchema).optional(),
|
|
109
|
+
/** Capabilities for the system/root agent. */
|
|
110
|
+
systemCapabilities: z.array(CapabilitySchema).optional(),
|
|
111
|
+
})
|
|
112
|
+
.strict()
|
|
113
|
+
.optional();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Engine - Pluggable conversation context management
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { LegacyContextEngine, globalContextEngineRegistry } from './context-engine/index.js';
|
|
7
|
+
*
|
|
8
|
+
* // Register the legacy engine
|
|
9
|
+
* globalContextEngineRegistry.register('legacy', LegacyContextEngine);
|
|
10
|
+
*
|
|
11
|
+
* // Get engine instance
|
|
12
|
+
* const engine = globalContextEngineRegistry.getEngine('legacy');
|
|
13
|
+
*
|
|
14
|
+
* // Bootstrap for a session
|
|
15
|
+
* await engine.bootstrap({ sessionId: 'session-123' });
|
|
16
|
+
*
|
|
17
|
+
* // Ingest a message
|
|
18
|
+
* await engine.ingest({
|
|
19
|
+
* sessionId: 'session-123',
|
|
20
|
+
* message: { role: 'user', content: 'Hello!' }
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Assemble context
|
|
24
|
+
* const result = await engine.assemble({
|
|
25
|
+
* sessionId: 'session-123',
|
|
26
|
+
* messages: []
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
// Registry
|
|
31
|
+
export { ContextEngineRegistry, globalContextEngineRegistry, resolveContextEngine, } from "./registry.js";
|
|
32
|
+
// Legacy Engine
|
|
33
|
+
export { LegacyContextEngine, createLegacyContextEngine, } from "./legacy.js";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy Context Engine
|
|
3
|
+
*
|
|
4
|
+
* Maintains PoolBot's original context management behavior.
|
|
5
|
+
* This is the default engine that preserves backward compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Strategy:
|
|
8
|
+
* - Simple message accumulation
|
|
9
|
+
* - Truncation-based overflow handling
|
|
10
|
+
* - Basic compaction via summarization
|
|
11
|
+
*/
|
|
12
|
+
export class LegacyContextEngine {
|
|
13
|
+
info = {
|
|
14
|
+
id: "legacy",
|
|
15
|
+
name: "Legacy Context Engine",
|
|
16
|
+
description: "PoolBot's original context management with simple accumulation and truncation",
|
|
17
|
+
supportsVectorSearch: false,
|
|
18
|
+
supportsFullTextSearch: false,
|
|
19
|
+
tokenLimit: undefined, // No explicit limit
|
|
20
|
+
};
|
|
21
|
+
sessions = new Map();
|
|
22
|
+
async bootstrap(params) {
|
|
23
|
+
try {
|
|
24
|
+
// Initialize session storage
|
|
25
|
+
this.sessions.set(params.sessionId, {
|
|
26
|
+
messages: [],
|
|
27
|
+
lastAccessed: new Date(),
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: error instanceof Error ? error.message : String(error),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async ingest(params) {
|
|
41
|
+
try {
|
|
42
|
+
const session = this.sessions.get(params.sessionId);
|
|
43
|
+
if (!session) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: `Session ${params.sessionId} not found`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
session.messages.push(params.message);
|
|
50
|
+
session.lastAccessed = new Date();
|
|
51
|
+
// Estimate tokens (rough approximation)
|
|
52
|
+
const content = typeof params.message.content === "string"
|
|
53
|
+
? params.message.content
|
|
54
|
+
: JSON.stringify(params.message.content);
|
|
55
|
+
const estimatedTokens = Math.ceil(content.length / 4);
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
tokensConsumed: estimatedTokens,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: error instanceof Error ? error.message : String(error),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async assemble(params) {
|
|
69
|
+
const session = this.sessions.get(params.sessionId);
|
|
70
|
+
const messages = session?.messages ?? params.messages;
|
|
71
|
+
// Apply token budget if specified
|
|
72
|
+
let resultMessages = messages;
|
|
73
|
+
let wasTruncated = false;
|
|
74
|
+
let excludedMessages = [];
|
|
75
|
+
if (params.tokenBudget && params.tokenBudget > 0) {
|
|
76
|
+
const estimate = this.estimateTokenCount(messages);
|
|
77
|
+
if (estimate > params.tokenBudget) {
|
|
78
|
+
// Simple truncation: keep newest messages that fit
|
|
79
|
+
const reversed = [...messages].reverse();
|
|
80
|
+
const fitting = [];
|
|
81
|
+
let currentTokens = 0;
|
|
82
|
+
for (const msg of reversed) {
|
|
83
|
+
const msgTokens = this.estimateTokenCount([msg]);
|
|
84
|
+
if (currentTokens + msgTokens <= params.tokenBudget) {
|
|
85
|
+
fitting.unshift(msg);
|
|
86
|
+
currentTokens += msgTokens;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
excludedMessages.unshift(msg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
resultMessages = fitting;
|
|
93
|
+
wasTruncated = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
messages: resultMessages,
|
|
98
|
+
totalTokens: this.estimateTokenCount(resultMessages),
|
|
99
|
+
wasTruncated,
|
|
100
|
+
excludedMessages: wasTruncated ? excludedMessages : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async compact(params) {
|
|
104
|
+
try {
|
|
105
|
+
const session = this.sessions.get(params.sessionId);
|
|
106
|
+
if (!session || session.messages.length === 0) {
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
tokensSaved: 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const originalTokens = this.estimateTokenCount(session.messages);
|
|
113
|
+
// Legacy compaction: summarize older messages
|
|
114
|
+
// Keep recent messages, summarize older ones
|
|
115
|
+
const keepRecent = 4; // Keep last 2 exchanges
|
|
116
|
+
const toSummarize = session.messages.slice(0, -keepRecent);
|
|
117
|
+
const toKeep = session.messages.slice(-keepRecent);
|
|
118
|
+
if (toSummarize.length === 0) {
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
tokensSaved: 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// Create summary message
|
|
125
|
+
const summary = {
|
|
126
|
+
role: "system",
|
|
127
|
+
content: `[${toSummarize.length} earlier messages summarized]`,
|
|
128
|
+
metadata: { compacted: true, originalCount: toSummarize.length },
|
|
129
|
+
};
|
|
130
|
+
session.messages = [summary, ...toKeep];
|
|
131
|
+
const newTokens = this.estimateTokenCount(session.messages);
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
summary: summary.content,
|
|
135
|
+
tokensSaved: originalTokens - newTokens,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: error instanceof Error ? error.message : String(error),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async cleanup(params) {
|
|
146
|
+
this.sessions.delete(params.sessionId);
|
|
147
|
+
}
|
|
148
|
+
async getStats(params) {
|
|
149
|
+
const session = this.sessions.get(params.sessionId);
|
|
150
|
+
if (!session) {
|
|
151
|
+
return {
|
|
152
|
+
messageCount: 0,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
messageCount: session.messages.length,
|
|
157
|
+
totalTokens: this.estimateTokenCount(session.messages),
|
|
158
|
+
lastAccessed: session.lastAccessed,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Estimate token count for messages
|
|
163
|
+
* Rough approximation: ~4 characters per token
|
|
164
|
+
*/
|
|
165
|
+
estimateTokenCount(messages) {
|
|
166
|
+
let totalChars = 0;
|
|
167
|
+
for (const msg of messages) {
|
|
168
|
+
const content = typeof msg.content === "string"
|
|
169
|
+
? msg.content
|
|
170
|
+
: JSON.stringify(msg.content);
|
|
171
|
+
totalChars += content.length;
|
|
172
|
+
}
|
|
173
|
+
return Math.ceil(totalChars / 4);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Create the default legacy context engine
|
|
178
|
+
*/
|
|
179
|
+
export function createLegacyContextEngine() {
|
|
180
|
+
return new LegacyContextEngine();
|
|
181
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Engine Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages available context engines and provides
|
|
5
|
+
* factory methods for creating engine instances.
|
|
6
|
+
*/
|
|
7
|
+
export class ContextEngineRegistry {
|
|
8
|
+
engines = new Map();
|
|
9
|
+
instances = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Register a new context engine
|
|
12
|
+
*/
|
|
13
|
+
register(id, EngineClass) {
|
|
14
|
+
this.engines.set(id, EngineClass);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if an engine is registered
|
|
18
|
+
*/
|
|
19
|
+
has(id) {
|
|
20
|
+
return this.engines.has(id);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get available engine IDs
|
|
24
|
+
*/
|
|
25
|
+
getAvailableEngines() {
|
|
26
|
+
return Array.from(this.engines.keys());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create or get a cached engine instance
|
|
30
|
+
*/
|
|
31
|
+
getEngine(id) {
|
|
32
|
+
// Return cached instance if exists
|
|
33
|
+
const cached = this.instances.get(id);
|
|
34
|
+
if (cached) {
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
// Create new instance
|
|
38
|
+
const EngineClass = this.engines.get(id);
|
|
39
|
+
if (!EngineClass) {
|
|
40
|
+
throw new Error(`Context engine "${id}" not registered. Available: ${this.getAvailableEngines().join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
const instance = new EngineClass();
|
|
43
|
+
this.instances.set(id, instance);
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Remove cached instance (for cleanup/testing)
|
|
48
|
+
*/
|
|
49
|
+
clearCache(id) {
|
|
50
|
+
if (id) {
|
|
51
|
+
this.instances.delete(id);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.instances.clear();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get engine info for all registered engines
|
|
59
|
+
*/
|
|
60
|
+
getEngineInfos() {
|
|
61
|
+
return this.getAvailableEngines().map((id) => {
|
|
62
|
+
const engine = this.getEngine(id);
|
|
63
|
+
return {
|
|
64
|
+
id,
|
|
65
|
+
name: engine.info.name,
|
|
66
|
+
description: engine.info.description,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Global registry instance
|
|
73
|
+
*/
|
|
74
|
+
export const globalContextEngineRegistry = new ContextEngineRegistry();
|
|
75
|
+
/**
|
|
76
|
+
* Resolve which engine to use based on configuration
|
|
77
|
+
*/
|
|
78
|
+
export function resolveContextEngine(sessionId, config, registry = globalContextEngineRegistry) {
|
|
79
|
+
// Check for session-specific override
|
|
80
|
+
const engineId = config.sessionEngines?.[sessionId] ?? config.defaultEngine;
|
|
81
|
+
if (!registry.has(engineId)) {
|
|
82
|
+
throw new Error(`Context engine "${engineId}" not found for session ${sessionId}. ` +
|
|
83
|
+
`Available engines: ${registry.getAvailableEngines().join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
return registry.getEngine(engineId);
|
|
86
|
+
}
|