qlogicagent 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -45
- package/package.json +56 -42
- package/dist/agent/agent.d.ts +0 -43
- package/dist/agent/agent.js +0 -113
- package/dist/agent/tool-loop.d.ts +0 -64
- package/dist/agent/tool-loop.js +0 -575
- package/dist/agent/types.d.ts +0 -175
- package/dist/agent/types.js +0 -14
- package/dist/cli/main.d.ts +0 -11
- package/dist/cli/main.js +0 -23
- package/dist/cli/stdio-server.d.ts +0 -45
- package/dist/cli/stdio-server.js +0 -463
- package/dist/config/config.d.ts +0 -17
- package/dist/config/config.js +0 -21
- package/dist/contracts/hooks.d.ts +0 -120
- package/dist/contracts/hooks.js +0 -7
- package/dist/contracts/index.d.ts +0 -10
- package/dist/contracts/index.js +0 -10
- package/dist/contracts/planner.d.ts +0 -35
- package/dist/contracts/planner.js +0 -2
- package/dist/contracts/skill-candidate.d.ts +0 -63
- package/dist/contracts/skill-candidate.js +0 -195
- package/dist/contracts/todo.d.ts +0 -14
- package/dist/contracts/todo.js +0 -9
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -15
- package/dist/llm/builtin-providers.d.ts +0 -10
- package/dist/llm/builtin-providers.js +0 -531
- package/dist/llm/index.d.ts +0 -15
- package/dist/llm/index.js +0 -14
- package/dist/llm/llm-client.d.ts +0 -43
- package/dist/llm/llm-client.js +0 -67
- package/dist/llm/model-catalog.d.ts +0 -53
- package/dist/llm/model-catalog.js +0 -191
- package/dist/llm/provider-def.d.ts +0 -59
- package/dist/llm/provider-def.js +0 -12
- package/dist/llm/provider-registry.d.ts +0 -54
- package/dist/llm/provider-registry.js +0 -147
- package/dist/llm/transport.d.ts +0 -62
- package/dist/llm/transport.js +0 -27
- package/dist/llm/transports/anthropic-messages.d.ts +0 -31
- package/dist/llm/transports/anthropic-messages.js +0 -293
- package/dist/llm/transports/openai-chat.d.ts +0 -36
- package/dist/llm/transports/openai-chat.js +0 -165
- package/dist/orchestration/agent-registry.d.ts +0 -41
- package/dist/orchestration/agent-registry.js +0 -116
- package/dist/orchestration/approval-aware-tool-plan.d.ts +0 -32
- package/dist/orchestration/approval-aware-tool-plan.js +0 -87
- package/dist/orchestration/context-compression.d.ts +0 -220
- package/dist/orchestration/context-compression.js +0 -583
- package/dist/orchestration/conversation-repair.d.ts +0 -61
- package/dist/orchestration/conversation-repair.js +0 -429
- package/dist/orchestration/curator-scheduler.d.ts +0 -119
- package/dist/orchestration/curator-scheduler.js +0 -135
- package/dist/orchestration/embedded-failover-policy.d.ts +0 -110
- package/dist/orchestration/embedded-failover-policy.js +0 -168
- package/dist/orchestration/error-classification.d.ts +0 -12
- package/dist/orchestration/error-classification.js +0 -77
- package/dist/orchestration/failover-classification.d.ts +0 -8
- package/dist/orchestration/failover-classification.js +0 -381
- package/dist/orchestration/failover-error.d.ts +0 -33
- package/dist/orchestration/failover-error.js +0 -198
- package/dist/orchestration/fork-subagent.d.ts +0 -100
- package/dist/orchestration/fork-subagent.js +0 -98
- package/dist/orchestration/index.d.ts +0 -120
- package/dist/orchestration/index.js +0 -267
- package/dist/orchestration/memory-flush-policy.d.ts +0 -57
- package/dist/orchestration/memory-flush-policy.js +0 -85
- package/dist/orchestration/memory-provider.d.ts +0 -14
- package/dist/orchestration/memory-provider.js +0 -2
- package/dist/orchestration/parallel-tool-calls.d.ts +0 -41
- package/dist/orchestration/parallel-tool-calls.js +0 -59
- package/dist/orchestration/prompt-cache-strategy.d.ts +0 -126
- package/dist/orchestration/prompt-cache-strategy.js +0 -228
- package/dist/orchestration/reactive-compact.d.ts +0 -73
- package/dist/orchestration/reactive-compact.js +0 -78
- package/dist/orchestration/retry-loop.d.ts +0 -22
- package/dist/orchestration/retry-loop.js +0 -24
- package/dist/orchestration/skill-candidate.d.ts +0 -52
- package/dist/orchestration/skill-candidate.js +0 -141
- package/dist/orchestration/skill-consolidation.d.ts +0 -123
- package/dist/orchestration/skill-consolidation.js +0 -220
- package/dist/orchestration/skill-improvement.d.ts +0 -59
- package/dist/orchestration/skill-improvement.js +0 -66
- package/dist/orchestration/skill-similarity.d.ts +0 -98
- package/dist/orchestration/skill-similarity.js +0 -131
- package/dist/orchestration/streaming-tool-executor.d.ts +0 -73
- package/dist/orchestration/streaming-tool-executor.js +0 -96
- package/dist/orchestration/team-orchestration.d.ts +0 -195
- package/dist/orchestration/team-orchestration.js +0 -369
- package/dist/orchestration/team-tool-loop-wiring.d.ts +0 -92
- package/dist/orchestration/team-tool-loop-wiring.js +0 -147
- package/dist/orchestration/tool-choice-policy.d.ts +0 -54
- package/dist/orchestration/tool-choice-policy.js +0 -164
- package/dist/orchestration/tool-loop-state.d.ts +0 -50
- package/dist/orchestration/tool-loop-state.js +0 -133
- package/dist/orchestration/tool-schema.d.ts +0 -39
- package/dist/orchestration/tool-schema.js +0 -297
- package/dist/orchestration/transcript-repair.d.ts +0 -42
- package/dist/orchestration/transcript-repair.js +0 -426
- package/dist/orchestration/turn-loop-guard.d.ts +0 -86
- package/dist/orchestration/turn-loop-guard.js +0 -92
- package/dist/orchestration/web-browser-policy.d.ts +0 -17
- package/dist/orchestration/web-browser-policy.js +0 -39
- package/dist/runtime/context-compression.d.ts +0 -61
- package/dist/runtime/context-compression.js +0 -274
- package/dist/runtime/hook-registry.d.ts +0 -12
- package/dist/runtime/hook-registry.js +0 -53
- package/dist/runtime/memory-hooks.d.ts +0 -23
- package/dist/runtime/memory-hooks.js +0 -65
- package/dist/runtime/tool-eligibility.d.ts +0 -59
- package/dist/runtime/tool-eligibility.js +0 -111
- package/dist/skills/index.d.ts +0 -108
- package/dist/skills/index.js +0 -82
- package/dist/skills/memory-extractor.d.ts +0 -64
- package/dist/skills/memory-extractor.js +0 -173
- package/dist/skills/memory-query-tool.d.ts +0 -43
- package/dist/skills/memory-query-tool.js +0 -127
- package/dist/skills/memory-store.d.ts +0 -66
- package/dist/skills/memory-store.js +0 -228
- package/dist/skills/memory-tool.d.ts +0 -67
- package/dist/skills/memory-tool.js +0 -192
- package/dist/skills/portable-tool.d.ts +0 -71
- package/dist/skills/portable-tool.js +0 -14
- package/dist/skills/qmemory-adapter.d.ts +0 -52
- package/dist/skills/qmemory-adapter.js +0 -165
- package/dist/skills/skill-frontmatter.d.ts +0 -19
- package/dist/skills/skill-frontmatter.js +0 -344
- package/dist/skills/skill-guard.d.ts +0 -23
- package/dist/skills/skill-guard.js +0 -229
- package/dist/skills/skill-loader.d.ts +0 -16
- package/dist/skills/skill-loader.js +0 -303
- package/dist/skills/skill-source.d.ts +0 -119
- package/dist/skills/skill-source.js +0 -126
- package/dist/skills/skill-types.d.ts +0 -199
- package/dist/skills/skill-types.js +0 -6
- package/dist/skills/think-tool.d.ts +0 -16
- package/dist/skills/think-tool.js +0 -59
- package/dist/skills/todo-tool.d.ts +0 -63
- package/dist/skills/todo-tool.js +0 -114
- package/dist/skills/tools/agent-tool.d.ts +0 -91
- package/dist/skills/tools/agent-tool.js +0 -142
- package/dist/skills/tools/apply-patch-tool.d.ts +0 -29
- package/dist/skills/tools/apply-patch-tool.js +0 -184
- package/dist/skills/tools/ask-user-tool.d.ts +0 -80
- package/dist/skills/tools/ask-user-tool.js +0 -121
- package/dist/skills/tools/brief-tool.d.ts +0 -74
- package/dist/skills/tools/brief-tool.js +0 -95
- package/dist/skills/tools/browser-tool.d.ts +0 -114
- package/dist/skills/tools/browser-tool.js +0 -155
- package/dist/skills/tools/checkpoint-tool.d.ts +0 -66
- package/dist/skills/tools/checkpoint-tool.js +0 -102
- package/dist/skills/tools/config-tool.d.ts +0 -63
- package/dist/skills/tools/config-tool.js +0 -143
- package/dist/skills/tools/cron-tool.d.ts +0 -116
- package/dist/skills/tools/cron-tool.js +0 -175
- package/dist/skills/tools/edit-tool.d.ts +0 -43
- package/dist/skills/tools/edit-tool.js +0 -70
- package/dist/skills/tools/exec-tool.d.ts +0 -102
- package/dist/skills/tools/exec-tool.js +0 -133
- package/dist/skills/tools/image-generate-tool.d.ts +0 -62
- package/dist/skills/tools/image-generate-tool.js +0 -67
- package/dist/skills/tools/instructions-tool.d.ts +0 -103
- package/dist/skills/tools/instructions-tool.js +0 -187
- package/dist/skills/tools/lsp-tool.d.ts +0 -153
- package/dist/skills/tools/lsp-tool.js +0 -227
- package/dist/skills/tools/mcp-client-types.d.ts +0 -269
- package/dist/skills/tools/mcp-client-types.js +0 -53
- package/dist/skills/tools/mcp-tool.d.ts +0 -249
- package/dist/skills/tools/mcp-tool.js +0 -503
- package/dist/skills/tools/memory-tool.d.ts +0 -74
- package/dist/skills/tools/memory-tool.js +0 -88
- package/dist/skills/tools/monitor-tool.d.ts +0 -113
- package/dist/skills/tools/monitor-tool.js +0 -131
- package/dist/skills/tools/music-generate-tool.d.ts +0 -55
- package/dist/skills/tools/music-generate-tool.js +0 -62
- package/dist/skills/tools/notify-tool.d.ts +0 -53
- package/dist/skills/tools/notify-tool.js +0 -62
- package/dist/skills/tools/patch-tool.d.ts +0 -45
- package/dist/skills/tools/patch-tool.js +0 -505
- package/dist/skills/tools/pdf-tool.d.ts +0 -66
- package/dist/skills/tools/pdf-tool.js +0 -88
- package/dist/skills/tools/plan-mode-tool.d.ts +0 -59
- package/dist/skills/tools/plan-mode-tool.js +0 -122
- package/dist/skills/tools/read-tool.d.ts +0 -51
- package/dist/skills/tools/read-tool.js +0 -84
- package/dist/skills/tools/repl-tool.d.ts +0 -70
- package/dist/skills/tools/repl-tool.js +0 -69
- package/dist/skills/tools/search-tool.d.ts +0 -112
- package/dist/skills/tools/search-tool.js +0 -225
- package/dist/skills/tools/send-message-tool.d.ts +0 -51
- package/dist/skills/tools/send-message-tool.js +0 -76
- package/dist/skills/tools/skill-list-tool.d.ts +0 -33
- package/dist/skills/tools/skill-list-tool.js +0 -54
- package/dist/skills/tools/skill-manage-tool.d.ts +0 -73
- package/dist/skills/tools/skill-manage-tool.js +0 -153
- package/dist/skills/tools/skill-view-tool.d.ts +0 -37
- package/dist/skills/tools/skill-view-tool.js +0 -72
- package/dist/skills/tools/sleep-tool.d.ts +0 -49
- package/dist/skills/tools/sleep-tool.js +0 -81
- package/dist/skills/tools/structured-output-tool.d.ts +0 -116
- package/dist/skills/tools/structured-output-tool.js +0 -176
- package/dist/skills/tools/task-tool.d.ts +0 -104
- package/dist/skills/tools/task-tool.js +0 -161
- package/dist/skills/tools/team-tool.d.ts +0 -89
- package/dist/skills/tools/team-tool.js +0 -105
- package/dist/skills/tools/tool-search-tool.d.ts +0 -51
- package/dist/skills/tools/tool-search-tool.js +0 -110
- package/dist/skills/tools/tts-tool.d.ts +0 -38
- package/dist/skills/tools/tts-tool.js +0 -45
- package/dist/skills/tools/video-edit-tool.d.ts +0 -69
- package/dist/skills/tools/video-edit-tool.js +0 -74
- package/dist/skills/tools/video-generate-tool.d.ts +0 -62
- package/dist/skills/tools/video-generate-tool.js +0 -66
- package/dist/skills/tools/video-merge-tool.d.ts +0 -105
- package/dist/skills/tools/video-merge-tool.js +0 -92
- package/dist/skills/tools/video-upscale-tool.d.ts +0 -45
- package/dist/skills/tools/video-upscale-tool.js +0 -52
- package/dist/skills/tools/web-fetch-tool.d.ts +0 -78
- package/dist/skills/tools/web-fetch-tool.js +0 -92
- package/dist/skills/tools/web-search-tool.d.ts +0 -57
- package/dist/skills/tools/web-search-tool.js +0 -86
- package/dist/skills/tools/worktree-tool.d.ts +0 -69
- package/dist/skills/tools/worktree-tool.js +0 -147
- package/dist/skills/tools/write-tool.d.ts +0 -45
- package/dist/skills/tools/write-tool.js +0 -81
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { ContextEngineRegistry, type CompressibleMessage, type CompressionResult, type CompressionStrategy, type AsyncCompressionStrategy, type SummarizeFn } from "../orchestration/index.js";
|
|
2
|
-
import type { HookRegistry } from "./hook-registry.js";
|
|
3
|
-
import type { RuntimeLogger } from "./hook-registry.js";
|
|
4
|
-
import type { LLMTransport } from "../llm/transport.js";
|
|
5
|
-
/** Rough token estimate: ~4 chars per token for mixed CJK/English. */
|
|
6
|
-
export declare function estimateTokens(msg: CompressibleMessage): number;
|
|
7
|
-
/** Estimate total tokens for a message array. */
|
|
8
|
-
export declare function estimateTotalTokens(messages: CompressibleMessage[]): number;
|
|
9
|
-
export declare function getCompressionMetrics(): import("../orchestration/context-compression.js").CompressionMetricsSnapshot;
|
|
10
|
-
export declare function getContextEngineRegistry(): ContextEngineRegistry;
|
|
11
|
-
/**
|
|
12
|
-
* Create a summarization callback using an injected LLM transport.
|
|
13
|
-
* For CLI mode: uses the same transport that powers the agent loop.
|
|
14
|
-
* Falls back to non-LLM summary when transport/apiKey not available.
|
|
15
|
-
*/
|
|
16
|
-
export declare function createSummarizeFn(log: RuntimeLogger, opts?: {
|
|
17
|
-
transport?: LLMTransport;
|
|
18
|
-
apiKey?: string;
|
|
19
|
-
model?: string;
|
|
20
|
-
}): SummarizeFn;
|
|
21
|
-
/** Phase 1: sync-only pipeline (ToolResultTrim + SlidingWindow) */
|
|
22
|
-
export declare function createSyncPipeline(): CompressionStrategy;
|
|
23
|
-
/** Phase 2+3: async pipeline with LLM summarization + cache awareness */
|
|
24
|
-
export declare function createAsyncPipeline(summarize: SummarizeFn, opts?: {
|
|
25
|
-
onCacheInvalidated?: (info: {
|
|
26
|
-
droppedCount: number;
|
|
27
|
-
strategy: string;
|
|
28
|
-
}) => void;
|
|
29
|
-
}): AsyncCompressionStrategy;
|
|
30
|
-
/** Phase 2.3: incremental compaction pipeline (for partial compact) */
|
|
31
|
-
export declare function createIncrementalPipeline(summarize: SummarizeFn, opts?: {
|
|
32
|
-
preserveRecentCount?: number;
|
|
33
|
-
onCacheInvalidated?: (info: {
|
|
34
|
-
droppedCount: number;
|
|
35
|
-
strategy: string;
|
|
36
|
-
}) => void;
|
|
37
|
-
}): AsyncCompressionStrategy;
|
|
38
|
-
export interface CompressOptions {
|
|
39
|
-
budget?: number;
|
|
40
|
-
model?: string;
|
|
41
|
-
pipeline?: CompressionStrategy;
|
|
42
|
-
allowLlm?: boolean;
|
|
43
|
-
summarize?: SummarizeFn;
|
|
44
|
-
sessionId?: string;
|
|
45
|
-
}
|
|
46
|
-
/** Synchronous compression (Phase 1 only). */
|
|
47
|
-
export declare function compressMessages(messages: CompressibleMessage[], opts?: CompressOptions): CompressionResult;
|
|
48
|
-
/** Async compression (Phase 2+) — supports LLM summarization. */
|
|
49
|
-
export declare function compressMessagesAsync(messages: CompressibleMessage[], opts: CompressOptions & {
|
|
50
|
-
summarize: SummarizeFn;
|
|
51
|
-
}): Promise<CompressionResult>;
|
|
52
|
-
export declare function registerDefaultContextEngine(log: RuntimeLogger, opts?: {
|
|
53
|
-
transport?: LLMTransport;
|
|
54
|
-
apiKey?: string;
|
|
55
|
-
model?: string;
|
|
56
|
-
}): void;
|
|
57
|
-
export declare function registerContextCompressionHook(hooks: HookRegistry, log: RuntimeLogger, opts?: {
|
|
58
|
-
transport?: LLMTransport;
|
|
59
|
-
apiKey?: string;
|
|
60
|
-
model?: string;
|
|
61
|
-
}): void;
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Context Compression Bridge — wires orchestration compression
|
|
3
|
-
// strategies into the Agent turn execution flow.
|
|
4
|
-
//
|
|
5
|
-
// Ported from src/core/runtime/context-compression-bridge.ts.
|
|
6
|
-
// Key change: createSummarizeFn() accepts injected LLM transport
|
|
7
|
-
// instead of hardcoding admin proxy URL. Falls back to sync summary
|
|
8
|
-
// when no LLM is available (CLI standalone mode).
|
|
9
|
-
//
|
|
10
|
-
// Phase 1 (P0): ToolResultTrim + SlidingWindow (no LLM needed)
|
|
11
|
-
// Phase 2 (P1): LLM Summarization — HeadTail + Incremental
|
|
12
|
-
// Phase 3 (P1): Prompt Cache awareness
|
|
13
|
-
// Phase 4 (P2): Adaptive threshold + metrics + engine plugin
|
|
14
|
-
// ============================================================
|
|
15
|
-
import { composeStrategies, composeAsyncStrategies, SlidingWindowStrategy, ToolResultTrimStrategy, HeadTailProtectedStrategy, IncrementalCompactStrategy, CacheAwareCompressionStrategy, CompressionMetricsCollector, ContextEngineRegistry, computeAdaptiveBudget, selectCompressionTier, isAsyncCompressionStrategy, } from "../orchestration/index.js";
|
|
16
|
-
// ── Token estimation ───────────────────────────────────────
|
|
17
|
-
/** Rough token estimate: ~4 chars per token for mixed CJK/English. */
|
|
18
|
-
export function estimateTokens(msg) {
|
|
19
|
-
const text = typeof msg.content === "string"
|
|
20
|
-
? msg.content
|
|
21
|
-
: msg.content != null
|
|
22
|
-
? JSON.stringify(msg.content)
|
|
23
|
-
: "";
|
|
24
|
-
return Math.ceil(text.length / 4);
|
|
25
|
-
}
|
|
26
|
-
/** Estimate total tokens for a message array. */
|
|
27
|
-
export function estimateTotalTokens(messages) {
|
|
28
|
-
let total = 0;
|
|
29
|
-
for (const msg of messages) {
|
|
30
|
-
total += estimateTokens(msg);
|
|
31
|
-
}
|
|
32
|
-
return total;
|
|
33
|
-
}
|
|
34
|
-
// ── Model context window lookup ────────────────────────────
|
|
35
|
-
const MODEL_CONTEXT_WINDOWS = {
|
|
36
|
-
"deepseek-chat": 64_000,
|
|
37
|
-
"deepseek-reasoner": 64_000,
|
|
38
|
-
"deepseek-v4-flash": 64_000,
|
|
39
|
-
"gpt-4o": 128_000,
|
|
40
|
-
"gpt-4o-mini": 128_000,
|
|
41
|
-
"claude-sonnet-4-20250514": 200_000,
|
|
42
|
-
"claude-3-5-haiku-20241022": 200_000,
|
|
43
|
-
"gemini-2.0-flash": 1_000_000,
|
|
44
|
-
"gemini-2.5-flash-preview-05-20": 1_000_000,
|
|
45
|
-
};
|
|
46
|
-
function getModelContextWindow(model) {
|
|
47
|
-
if (!model)
|
|
48
|
-
return 128_000;
|
|
49
|
-
if (model in MODEL_CONTEXT_WINDOWS)
|
|
50
|
-
return MODEL_CONTEXT_WINDOWS[model];
|
|
51
|
-
const lower = model.toLowerCase();
|
|
52
|
-
for (const [key, value] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
53
|
-
if (lower.startsWith(key.toLowerCase()))
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
return 128_000;
|
|
57
|
-
}
|
|
58
|
-
// ── Constants ──────────────────────────────────────────────
|
|
59
|
-
const DEFAULT_TOOL_RESULT_MAX_CHARS = 8_000;
|
|
60
|
-
const SUMMARIZE_MODEL = "deepseek-chat";
|
|
61
|
-
// ── Singleton metrics collector (Phase 4) ──────────────────
|
|
62
|
-
const metricsCollector = new CompressionMetricsCollector(200);
|
|
63
|
-
export function getCompressionMetrics() {
|
|
64
|
-
return metricsCollector.snapshot();
|
|
65
|
-
}
|
|
66
|
-
// ── Singleton context engine registry (Phase 4.3) ──────────
|
|
67
|
-
const engineRegistry = new ContextEngineRegistry();
|
|
68
|
-
export function getContextEngineRegistry() {
|
|
69
|
-
return engineRegistry;
|
|
70
|
-
}
|
|
71
|
-
// ── LLM Summarization Callback Factory ─────────────────────
|
|
72
|
-
/**
|
|
73
|
-
* Create a summarization callback using an injected LLM transport.
|
|
74
|
-
* For CLI mode: uses the same transport that powers the agent loop.
|
|
75
|
-
* Falls back to non-LLM summary when transport/apiKey not available.
|
|
76
|
-
*/
|
|
77
|
-
export function createSummarizeFn(log, opts) {
|
|
78
|
-
return async (messages, instruction) => {
|
|
79
|
-
const transport = opts?.transport;
|
|
80
|
-
const apiKey = opts?.apiKey;
|
|
81
|
-
if (!transport || !apiKey) {
|
|
82
|
-
log.debug("[context-compression] no LLM transport for summarization — using sync fallback");
|
|
83
|
-
return fallbackSyncSummary(messages);
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
let content = "";
|
|
87
|
-
const model = opts?.model ?? SUMMARIZE_MODEL;
|
|
88
|
-
for await (const chunk of transport.stream({
|
|
89
|
-
model,
|
|
90
|
-
messages: [
|
|
91
|
-
{ role: "system", content: "You are a precise conversation summarizer." },
|
|
92
|
-
{ role: "user", content: instruction },
|
|
93
|
-
],
|
|
94
|
-
maxTokens: 2000,
|
|
95
|
-
temperature: 0.3,
|
|
96
|
-
}, apiKey, AbortSignal.timeout(30_000))) {
|
|
97
|
-
if (chunk.type === "delta")
|
|
98
|
-
content += chunk.text;
|
|
99
|
-
}
|
|
100
|
-
if (!content) {
|
|
101
|
-
log.warn("[context-compression] empty summary response");
|
|
102
|
-
return fallbackSyncSummary(messages);
|
|
103
|
-
}
|
|
104
|
-
return content;
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
log.warn({ err: err.message }, "[context-compression] summarize call error — using fallback");
|
|
108
|
-
return fallbackSyncSummary(messages);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/** Non-LLM fallback: extract key user messages as bullet points. */
|
|
113
|
-
function fallbackSyncSummary(messages) {
|
|
114
|
-
const userMsgs = messages.filter((m) => m.role === "user");
|
|
115
|
-
const bullets = userMsgs.slice(0, 10).map((m) => {
|
|
116
|
-
const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
|
|
117
|
-
return `- ${text.slice(0, 200)}${text.length > 200 ? "..." : ""}`;
|
|
118
|
-
});
|
|
119
|
-
return `User requests (${userMsgs.length} messages):\n${bullets.join("\n")}`;
|
|
120
|
-
}
|
|
121
|
-
// ── Pipeline factories ─────────────────────────────────────
|
|
122
|
-
/** Phase 1: sync-only pipeline (ToolResultTrim + SlidingWindow) */
|
|
123
|
-
export function createSyncPipeline() {
|
|
124
|
-
return composeStrategies(new ToolResultTrimStrategy(DEFAULT_TOOL_RESULT_MAX_CHARS), new SlidingWindowStrategy(estimateTokens));
|
|
125
|
-
}
|
|
126
|
-
/** Phase 2+3: async pipeline with LLM summarization + cache awareness */
|
|
127
|
-
export function createAsyncPipeline(summarize, opts) {
|
|
128
|
-
const innerPipeline = composeAsyncStrategies(new ToolResultTrimStrategy(DEFAULT_TOOL_RESULT_MAX_CHARS), new HeadTailProtectedStrategy({
|
|
129
|
-
protectedHeadExchanges: 1,
|
|
130
|
-
protectedTailMessages: 8,
|
|
131
|
-
summarize,
|
|
132
|
-
estimateTokens,
|
|
133
|
-
}), new SlidingWindowStrategy(estimateTokens));
|
|
134
|
-
return new CacheAwareCompressionStrategy({
|
|
135
|
-
inner: innerPipeline,
|
|
136
|
-
estimateTokens,
|
|
137
|
-
onCacheInvalidated: opts?.onCacheInvalidated,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
/** Phase 2.3: incremental compaction pipeline (for partial compact) */
|
|
141
|
-
export function createIncrementalPipeline(summarize, opts) {
|
|
142
|
-
const innerPipeline = composeAsyncStrategies(new ToolResultTrimStrategy(DEFAULT_TOOL_RESULT_MAX_CHARS), new IncrementalCompactStrategy({
|
|
143
|
-
preserveRecentCount: opts?.preserveRecentCount ?? 12,
|
|
144
|
-
summarize,
|
|
145
|
-
estimateTokens,
|
|
146
|
-
}), new SlidingWindowStrategy(estimateTokens));
|
|
147
|
-
return new CacheAwareCompressionStrategy({
|
|
148
|
-
inner: innerPipeline,
|
|
149
|
-
estimateTokens,
|
|
150
|
-
onCacheInvalidated: opts?.onCacheInvalidated,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/** Synchronous compression (Phase 1 only). */
|
|
154
|
-
export function compressMessages(messages, opts) {
|
|
155
|
-
const budget = opts?.budget ?? computeAdaptiveBudget({
|
|
156
|
-
modelContextWindow: getModelContextWindow(opts?.model),
|
|
157
|
-
});
|
|
158
|
-
const pipeline = opts?.pipeline ?? createSyncPipeline();
|
|
159
|
-
const result = pipeline.compress(messages, budget);
|
|
160
|
-
if (result.droppedCount > 0) {
|
|
161
|
-
const tokensBefore = estimateTotalTokens(messages);
|
|
162
|
-
const tokensAfter = estimateTotalTokens(result.messages);
|
|
163
|
-
metricsCollector.record({
|
|
164
|
-
timestamp: Date.now(),
|
|
165
|
-
strategy: result.strategy,
|
|
166
|
-
tokensBefore,
|
|
167
|
-
tokensAfter,
|
|
168
|
-
droppedCount: result.droppedCount,
|
|
169
|
-
latencyMs: result.metrics?.latencyMs ?? 0,
|
|
170
|
-
usedLlm: false,
|
|
171
|
-
cacheInvalidated: result.metrics?.cacheInvalidated ?? false,
|
|
172
|
-
tier: selectCompressionTier(tokensBefore, budget),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
/** Async compression (Phase 2+) — supports LLM summarization. */
|
|
178
|
-
export async function compressMessagesAsync(messages, opts) {
|
|
179
|
-
const budget = opts.budget ?? computeAdaptiveBudget({
|
|
180
|
-
modelContextWindow: getModelContextWindow(opts.model),
|
|
181
|
-
});
|
|
182
|
-
const activeEngine = engineRegistry.getActive();
|
|
183
|
-
if (activeEngine) {
|
|
184
|
-
const result = await activeEngine.compressAsync(messages, budget, {
|
|
185
|
-
model: opts.model,
|
|
186
|
-
sessionId: opts.sessionId,
|
|
187
|
-
});
|
|
188
|
-
recordMetrics(messages, result, budget);
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
return compressWithTierSelection(messages, opts, budget);
|
|
192
|
-
}
|
|
193
|
-
async function compressWithTierSelection(messages, opts, budget) {
|
|
194
|
-
const effectiveBudget = budget ?? computeAdaptiveBudget({
|
|
195
|
-
modelContextWindow: getModelContextWindow(opts.model),
|
|
196
|
-
});
|
|
197
|
-
const currentTokens = estimateTotalTokens(messages);
|
|
198
|
-
const tier = selectCompressionTier(currentTokens, effectiveBudget);
|
|
199
|
-
let result;
|
|
200
|
-
switch (tier) {
|
|
201
|
-
case "none":
|
|
202
|
-
result = { messages, droppedCount: 0, strategy: "none" };
|
|
203
|
-
break;
|
|
204
|
-
case "trim-only":
|
|
205
|
-
result = new ToolResultTrimStrategy(DEFAULT_TOOL_RESULT_MAX_CHARS).compress(messages, effectiveBudget);
|
|
206
|
-
break;
|
|
207
|
-
case "sliding-window": {
|
|
208
|
-
const swPipeline = createAsyncPipeline(opts.summarize);
|
|
209
|
-
result = await swPipeline.compressAsync(messages, effectiveBudget);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case "llm-summarize": {
|
|
213
|
-
const pipeline = opts.pipeline ?? createAsyncPipeline(opts.summarize);
|
|
214
|
-
result = isAsyncCompressionStrategy(pipeline)
|
|
215
|
-
? await pipeline.compressAsync(messages, effectiveBudget)
|
|
216
|
-
: pipeline.compress(messages, effectiveBudget);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
recordMetrics(messages, result, effectiveBudget);
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
|
-
function recordMetrics(messagesBefore, result, budget) {
|
|
224
|
-
if (result.droppedCount > 0 || result.metrics?.usedLlm) {
|
|
225
|
-
const tokensBefore = result.metrics?.tokensBefore || estimateTotalTokens(messagesBefore);
|
|
226
|
-
const tokensAfter = result.metrics?.tokensAfter || estimateTotalTokens(result.messages);
|
|
227
|
-
metricsCollector.record({
|
|
228
|
-
timestamp: Date.now(),
|
|
229
|
-
strategy: result.strategy,
|
|
230
|
-
tokensBefore,
|
|
231
|
-
tokensAfter,
|
|
232
|
-
droppedCount: result.droppedCount,
|
|
233
|
-
latencyMs: result.metrics?.latencyMs ?? 0,
|
|
234
|
-
usedLlm: result.metrics?.usedLlm ?? false,
|
|
235
|
-
cacheInvalidated: result.metrics?.cacheInvalidated ?? false,
|
|
236
|
-
tier: selectCompressionTier(tokensBefore, budget),
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// ── Default Context Engine (Phase 4.3) ─────────────────────
|
|
241
|
-
export function registerDefaultContextEngine(log, opts) {
|
|
242
|
-
const summarize = createSummarizeFn(log, opts);
|
|
243
|
-
const engine = {
|
|
244
|
-
id: "builtin-compressor",
|
|
245
|
-
label: "4-Layer Compression Funnel (built-in)",
|
|
246
|
-
async compressAsync(messages, budget, context) {
|
|
247
|
-
return compressWithTierSelection(messages, {
|
|
248
|
-
budget,
|
|
249
|
-
model: context?.model,
|
|
250
|
-
sessionId: context?.sessionId,
|
|
251
|
-
summarize,
|
|
252
|
-
}, budget);
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
engineRegistry.register(engine);
|
|
256
|
-
engineRegistry.activate(engine.id);
|
|
257
|
-
log.info(`[context-compression] registered context engine: ${engine.id}`);
|
|
258
|
-
}
|
|
259
|
-
// ── Hook registration ──────────────────────────────────────
|
|
260
|
-
export function registerContextCompressionHook(hooks, log, opts) {
|
|
261
|
-
registerDefaultContextEngine(log, opts);
|
|
262
|
-
hooks.register({
|
|
263
|
-
point: "context.before_compact",
|
|
264
|
-
priority: 50,
|
|
265
|
-
label: "context-compression-bridge",
|
|
266
|
-
handler: (_point, context) => {
|
|
267
|
-
const messageCount = context.messageCount;
|
|
268
|
-
if (messageCount && messageCount > 0) {
|
|
269
|
-
log.debug(`[context-compression] before_compact: ${messageCount} messages entering compression`);
|
|
270
|
-
}
|
|
271
|
-
return { action: "continue" };
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { HookPoint, HookContextMap, HookResult, HookHandler, HookRegistration, HookRegistry } from "../contracts/hooks.js";
|
|
2
|
-
export type { HookPoint, HookContextMap, HookResult, HookHandler, HookRegistration, HookRegistry };
|
|
3
|
-
/** Minimal logger interface — compatible with both pino and AgentLogger. */
|
|
4
|
-
export interface RuntimeLogger {
|
|
5
|
-
info(msg: string): void;
|
|
6
|
-
info(obj: Record<string, unknown>, msg: string): void;
|
|
7
|
-
warn(msg: string): void;
|
|
8
|
-
warn(obj: Record<string, unknown>, msg: string): void;
|
|
9
|
-
debug(msg: string): void;
|
|
10
|
-
debug(obj: Record<string, unknown>, msg: string): void;
|
|
11
|
-
}
|
|
12
|
-
export declare function createHookRegistry(log: RuntimeLogger): HookRegistry;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Hook registry — manages lifecycle hooks for the Agent Runtime.
|
|
3
|
-
// Hook failures never block the main flow (catch + log).
|
|
4
|
-
//
|
|
5
|
-
// Ported from src/core/runtime/hook-registry.ts (zero changes to logic).
|
|
6
|
-
// ============================================================
|
|
7
|
-
export function createHookRegistry(log) {
|
|
8
|
-
const handlers = new Map();
|
|
9
|
-
return {
|
|
10
|
-
register(registration) {
|
|
11
|
-
const list = handlers.get(registration.point) ?? [];
|
|
12
|
-
const entry = {
|
|
13
|
-
handler: registration.handler,
|
|
14
|
-
priority: registration.priority ?? 100,
|
|
15
|
-
label: registration.label,
|
|
16
|
-
};
|
|
17
|
-
list.push(entry);
|
|
18
|
-
list.sort((a, b) => a.priority - b.priority);
|
|
19
|
-
handlers.set(registration.point, list);
|
|
20
|
-
return () => {
|
|
21
|
-
const idx = list.indexOf(entry);
|
|
22
|
-
if (idx >= 0)
|
|
23
|
-
list.splice(idx, 1);
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
async invoke(point, context) {
|
|
27
|
-
const list = handlers.get(point);
|
|
28
|
-
if (!list || list.length === 0) {
|
|
29
|
-
return { action: "continue", context };
|
|
30
|
-
}
|
|
31
|
-
let current = context;
|
|
32
|
-
for (const entry of list) {
|
|
33
|
-
try {
|
|
34
|
-
const result = await entry.handler(point, current);
|
|
35
|
-
if (!result || result.action === "continue") {
|
|
36
|
-
if (result?.context) {
|
|
37
|
-
current = result.context;
|
|
38
|
-
}
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (result.action === "abort" || result.action === "skip") {
|
|
42
|
-
log.info({ hook: point, label: entry.label, action: result.action, reason: result.reason }, "hook intercepted");
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
log.warn({ hook: point, label: entry.label, err: err.message }, "hook handler failed (non-blocking)");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return { action: "continue", context: current };
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { HookRegistry } from "../contracts/hooks.js";
|
|
2
|
-
import type { MemoryProvider } from "qlogicagent-runtime-contracts";
|
|
3
|
-
export interface MemoryHooksDeps {
|
|
4
|
-
/** QMemory adapter (MemoryProvider with search/health). */
|
|
5
|
-
memoryProvider?: MemoryProvider;
|
|
6
|
-
/** User ID for memory lookups. */
|
|
7
|
-
userId: string;
|
|
8
|
-
/** Logger. */
|
|
9
|
-
log: {
|
|
10
|
-
debug(msg: string): void;
|
|
11
|
-
warn(msg: string): void;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Register memory lifecycle hooks on the HookRegistry.
|
|
16
|
-
*
|
|
17
|
-
* Hook points wired:
|
|
18
|
-
* - memory.before_recall: prefetch context from qmemory (query-based)
|
|
19
|
-
* - memory.after_recall: log recall results
|
|
20
|
-
*
|
|
21
|
-
* Returns a cleanup function to unregister.
|
|
22
|
-
*/
|
|
23
|
-
export declare function registerMemoryHooks(hooks: HookRegistry, deps: MemoryHooksDeps): () => void;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Memory lifecycle hooks — CLI runtime
|
|
3
|
-
//
|
|
4
|
-
// Wires memory.before_recall / memory.after_recall hook points
|
|
5
|
-
// into the qmemory REST API. Registered once per session when
|
|
6
|
-
// the HookRegistry is created.
|
|
7
|
-
//
|
|
8
|
-
// Unlike the Hub version (PG-based), this uses the local
|
|
9
|
-
// qmemory HTTP adapter for recall prefetch.
|
|
10
|
-
// ============================================================
|
|
11
|
-
/**
|
|
12
|
-
* Register memory lifecycle hooks on the HookRegistry.
|
|
13
|
-
*
|
|
14
|
-
* Hook points wired:
|
|
15
|
-
* - memory.before_recall: prefetch context from qmemory (query-based)
|
|
16
|
-
* - memory.after_recall: log recall results
|
|
17
|
-
*
|
|
18
|
-
* Returns a cleanup function to unregister.
|
|
19
|
-
*/
|
|
20
|
-
export function registerMemoryHooks(hooks, deps) {
|
|
21
|
-
const cleanups = [];
|
|
22
|
-
// Shared recall cache per turn (avoids redundant queries)
|
|
23
|
-
let lastRecallTurnId;
|
|
24
|
-
let lastRecallResults = [];
|
|
25
|
-
// memory.before_recall — prefetch from qmemory
|
|
26
|
-
cleanups.push(hooks.register({
|
|
27
|
-
point: "memory.before_recall",
|
|
28
|
-
priority: 50,
|
|
29
|
-
label: "qmemory-prefetch",
|
|
30
|
-
handler: async (_point, ctx) => {
|
|
31
|
-
if (!deps.memoryProvider || !ctx.query) {
|
|
32
|
-
return { action: "continue", context: ctx };
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const results = await deps.memoryProvider.search(ctx.query, deps.userId, {
|
|
36
|
-
limit: 10,
|
|
37
|
-
});
|
|
38
|
-
lastRecallTurnId = ctx.turnId;
|
|
39
|
-
lastRecallResults = results;
|
|
40
|
-
deps.log.debug(`memory.before_recall: prefetched ${results.length} memories for query="${ctx.query?.slice(0, 60)}"`);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
deps.log.warn(`memory.before_recall: qmemory prefetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
-
lastRecallResults = [];
|
|
45
|
-
}
|
|
46
|
-
return { action: "continue", context: ctx };
|
|
47
|
-
},
|
|
48
|
-
}));
|
|
49
|
-
// memory.after_recall — log results
|
|
50
|
-
cleanups.push(hooks.register({
|
|
51
|
-
point: "memory.after_recall",
|
|
52
|
-
priority: 50,
|
|
53
|
-
label: "qmemory-recall-log",
|
|
54
|
-
handler: (_point, ctx) => {
|
|
55
|
-
const count = lastRecallTurnId === ctx.turnId ? lastRecallResults.length : 0;
|
|
56
|
-
deps.log.debug(`memory.after_recall: ${ctx.blockCount ?? 0} blocks assembled, ${count} qmemory results`);
|
|
57
|
-
return { action: "continue", context: ctx };
|
|
58
|
-
},
|
|
59
|
-
}));
|
|
60
|
-
return () => {
|
|
61
|
-
for (const cleanup of cleanups)
|
|
62
|
-
cleanup();
|
|
63
|
-
lastRecallResults = [];
|
|
64
|
-
};
|
|
65
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool eligibility resolver for CLI subprocess mode.
|
|
3
|
-
*
|
|
4
|
-
* Ported from src/core/runtime/tool-eligibility.ts, replacing Hub's
|
|
5
|
-
* StoredGatewayCapabilitySnapshot with CLI's ToolDefinition[] from agent.turn.
|
|
6
|
-
*
|
|
7
|
-
* Core decision logic preserved:
|
|
8
|
-
* - Policy blocked (explicit block list)
|
|
9
|
-
* - Approval required (meta.requiresApproval / meta.isDangerous)
|
|
10
|
-
* - Read-only / safe tools (meta.isReadOnly → skip approval even in strict)
|
|
11
|
-
* - All tools from Gateway manifest are considered enabled & reachable
|
|
12
|
-
* (Gateway only sends available tools)
|
|
13
|
-
*
|
|
14
|
-
* Zero imports from express/pg/ioredis/ws.
|
|
15
|
-
*/
|
|
16
|
-
import type { ToolDefinition } from "../agent/types.js";
|
|
17
|
-
/**
|
|
18
|
-
* Tool permission levels aligned with Claude Code's permission model:
|
|
19
|
-
*
|
|
20
|
-
* Level 1 — always-allow: isReadOnly tools (e.g. read_file, search)
|
|
21
|
-
* Level 2 — default-allow: normal tools with no risk markers
|
|
22
|
-
* Level 3 — notify: isDangerous tools → emit tool_blocked warning but allow
|
|
23
|
-
* Level 4 — ask: requiresApproval tools → hook can block, emit approval.requested
|
|
24
|
-
* Level 5 — never-allow: explicitly blocked by policy
|
|
25
|
-
*/
|
|
26
|
-
export type PermissionLevel = 1 | 2 | 3 | 4 | 5;
|
|
27
|
-
export type ToolEligibilityStatus = "eligible" | "approval-required" | "blocked-by-policy" | "dangerous-notify";
|
|
28
|
-
export type ToolEligibilityReasonCode = "policy_blocked" | "approval_required" | "dangerous_tool" | "always_allowed";
|
|
29
|
-
export interface ToolEligibilityEntry {
|
|
30
|
-
toolName: string;
|
|
31
|
-
status: ToolEligibilityStatus;
|
|
32
|
-
permissionLevel: PermissionLevel;
|
|
33
|
-
approvalRequired: boolean;
|
|
34
|
-
reasonCodes: ToolEligibilityReasonCode[];
|
|
35
|
-
}
|
|
36
|
-
export interface ToolEligibilityContext {
|
|
37
|
-
/** Explicitly blocked tool names (e.g. from config or policy) */
|
|
38
|
-
blockedToolNames?: string[];
|
|
39
|
-
/** Dangerous tool name patterns (regex strings) for pattern-based detection */
|
|
40
|
-
dangerousPatterns?: string[];
|
|
41
|
-
}
|
|
42
|
-
export interface ToolEligibilityResult {
|
|
43
|
-
/** Tools eligible for execution (includes approval-required ones) */
|
|
44
|
-
eligibleTools: ToolDefinition[];
|
|
45
|
-
/** Tools blocked by policy (removed from LLM tool list) */
|
|
46
|
-
blockedTools: ToolEligibilityEntry[];
|
|
47
|
-
/** Tools needing approval before execution */
|
|
48
|
-
approvalRequiredTools: ToolEligibilityEntry[];
|
|
49
|
-
/** Full eligibility map by tool name */
|
|
50
|
-
eligibilityByName: Map<string, ToolEligibilityEntry>;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Resolve tool eligibility for a set of tools from agent.turn.
|
|
54
|
-
*
|
|
55
|
-
* Returns filtered tool lists and eligibility metadata.
|
|
56
|
-
* Blocked tools are removed from the eligible list (not sent to LLM).
|
|
57
|
-
* Approval-required tools remain in the list but flagged for hook interception.
|
|
58
|
-
*/
|
|
59
|
-
export declare function resolveToolEligibility(tools: ToolDefinition[], context?: ToolEligibilityContext): ToolEligibilityResult;
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool eligibility resolver for CLI subprocess mode.
|
|
3
|
-
*
|
|
4
|
-
* Ported from src/core/runtime/tool-eligibility.ts, replacing Hub's
|
|
5
|
-
* StoredGatewayCapabilitySnapshot with CLI's ToolDefinition[] from agent.turn.
|
|
6
|
-
*
|
|
7
|
-
* Core decision logic preserved:
|
|
8
|
-
* - Policy blocked (explicit block list)
|
|
9
|
-
* - Approval required (meta.requiresApproval / meta.isDangerous)
|
|
10
|
-
* - Read-only / safe tools (meta.isReadOnly → skip approval even in strict)
|
|
11
|
-
* - All tools from Gateway manifest are considered enabled & reachable
|
|
12
|
-
* (Gateway only sends available tools)
|
|
13
|
-
*
|
|
14
|
-
* Zero imports from express/pg/ioredis/ws.
|
|
15
|
-
*/
|
|
16
|
-
// ── Dangerous pattern detection (CC parity) ──────────────────────────────────
|
|
17
|
-
const BUILTIN_DANGEROUS_PATTERNS = [
|
|
18
|
-
/^(?:bash|shell|exec|terminal|run_command)$/i,
|
|
19
|
-
/^(?:write_file|delete_file|move_file|create_directory)$/i,
|
|
20
|
-
/^(?:git_push|git_reset|git_force)$/i,
|
|
21
|
-
];
|
|
22
|
-
function matchesDangerousPattern(toolName, extraPatterns) {
|
|
23
|
-
if (BUILTIN_DANGEROUS_PATTERNS.some((re) => re.test(toolName))) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
if (extraPatterns) {
|
|
27
|
-
for (const pattern of extraPatterns) {
|
|
28
|
-
try {
|
|
29
|
-
if (new RegExp(pattern, "i").test(toolName))
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Invalid regex — skip
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
// ── Core resolver ────────────────────────────────────────────────────────────
|
|
40
|
-
function resolvePermissionLevel(tool, context) {
|
|
41
|
-
const name = tool.function.name;
|
|
42
|
-
const meta = tool.meta;
|
|
43
|
-
const reasons = [];
|
|
44
|
-
// Level 5: explicitly blocked
|
|
45
|
-
if (context.blockedToolNames?.includes(name)) {
|
|
46
|
-
reasons.push("policy_blocked");
|
|
47
|
-
return { level: 5, reasons };
|
|
48
|
-
}
|
|
49
|
-
// Level 1: always allowed (read-only / safe)
|
|
50
|
-
if (meta?.isReadOnly) {
|
|
51
|
-
reasons.push("always_allowed");
|
|
52
|
-
return { level: 1, reasons };
|
|
53
|
-
}
|
|
54
|
-
// Level 4: explicit approval required
|
|
55
|
-
if (meta?.requiresApproval) {
|
|
56
|
-
reasons.push("approval_required");
|
|
57
|
-
return { level: 4, reasons };
|
|
58
|
-
}
|
|
59
|
-
// Level 3: dangerous (by meta flag or pattern detection)
|
|
60
|
-
if (meta?.isDangerous || matchesDangerousPattern(name, context.dangerousPatterns)) {
|
|
61
|
-
reasons.push("dangerous_tool");
|
|
62
|
-
return { level: 3, reasons };
|
|
63
|
-
}
|
|
64
|
-
// Level 2: default allowed
|
|
65
|
-
return { level: 2, reasons };
|
|
66
|
-
}
|
|
67
|
-
function levelToStatus(level) {
|
|
68
|
-
switch (level) {
|
|
69
|
-
case 1: return "eligible";
|
|
70
|
-
case 2: return "eligible";
|
|
71
|
-
case 3: return "dangerous-notify";
|
|
72
|
-
case 4: return "approval-required";
|
|
73
|
-
case 5: return "blocked-by-policy";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Resolve tool eligibility for a set of tools from agent.turn.
|
|
78
|
-
*
|
|
79
|
-
* Returns filtered tool lists and eligibility metadata.
|
|
80
|
-
* Blocked tools are removed from the eligible list (not sent to LLM).
|
|
81
|
-
* Approval-required tools remain in the list but flagged for hook interception.
|
|
82
|
-
*/
|
|
83
|
-
export function resolveToolEligibility(tools, context = {}) {
|
|
84
|
-
const eligibilityByName = new Map();
|
|
85
|
-
const eligibleTools = [];
|
|
86
|
-
const blockedTools = [];
|
|
87
|
-
const approvalRequiredTools = [];
|
|
88
|
-
for (const tool of tools) {
|
|
89
|
-
const name = tool.function.name;
|
|
90
|
-
const { level, reasons } = resolvePermissionLevel(tool, context);
|
|
91
|
-
const status = levelToStatus(level);
|
|
92
|
-
const entry = {
|
|
93
|
-
toolName: name,
|
|
94
|
-
status,
|
|
95
|
-
permissionLevel: level,
|
|
96
|
-
approvalRequired: level === 4,
|
|
97
|
-
reasonCodes: reasons,
|
|
98
|
-
};
|
|
99
|
-
eligibilityByName.set(name, entry);
|
|
100
|
-
if (level === 5) {
|
|
101
|
-
blockedTools.push(entry);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
eligibleTools.push(tool);
|
|
105
|
-
if (level === 4) {
|
|
106
|
-
approvalRequiredTools.push(entry);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { eligibleTools, blockedTools, approvalRequiredTools, eligibilityByName };
|
|
111
|
-
}
|