@loreai/gateway 0.14.0 → 0.15.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/src/compaction.ts DELETED
@@ -1,195 +0,0 @@
1
- /**
2
- * Compaction request detection and interception for the Lore gateway.
3
- *
4
- * Claude Code (and other clients using the same pattern) sends compaction
5
- * requests with a distinct system prompt and message structure. The gateway
6
- * detects these and runs Lore's own distillation instead of forwarding to
7
- * the upstream API.
8
- *
9
- * Detection mirrors the patterns documented in the upstream
10
- * `packages/opencode/src/agent/prompt/compaction.txt` and the
11
- * `experimental.session.compacting` hook.
12
- *
13
- * This module has zero dependencies on `@loreai/core` — pure detection logic.
14
- */
15
- import type { GatewayRequest, GatewayResponse } from "./translate/types";
16
-
17
- // ---------------------------------------------------------------------------
18
- // Detection patterns — exported so tests can reference them
19
- // ---------------------------------------------------------------------------
20
-
21
- /** System prompt substrings that identify a compaction agent. */
22
- export const COMPACTION_SYSTEM_PATTERNS = [
23
- "anchored context summarization assistant",
24
- ] as const;
25
-
26
- /** Last user message substrings that indicate a compaction request. */
27
- export const COMPACTION_USER_PATTERNS = [
28
- "anchored summary from the conversation history above",
29
- "Update the anchored summary below",
30
- "<previous-summary>",
31
- ] as const;
32
-
33
- /**
34
- * Template section headers found in the `<template>` block of a compaction
35
- * request. A request matching ≥4 of these (with a `<template>` tag) is
36
- * considered a compaction request.
37
- */
38
- export const COMPACTION_TEMPLATE_SECTIONS = [
39
- "## Goal",
40
- "## Progress",
41
- "## Key Decisions",
42
- "## Next Steps",
43
- "## Critical Context",
44
- "## Relevant Files",
45
- ] as const;
46
-
47
- /** Minimum number of template sections that must match (with `<template>` tag). */
48
- const MIN_TEMPLATE_SECTION_MATCHES = 4;
49
-
50
- // ---------------------------------------------------------------------------
51
- // Helpers
52
- // ---------------------------------------------------------------------------
53
-
54
- /**
55
- * Extract the concatenated text content from the last user message.
56
- * Returns an empty string if there are no user messages or no text blocks.
57
- */
58
- function lastUserText(req: GatewayRequest): string {
59
- for (let i = req.messages.length - 1; i >= 0; i--) {
60
- const msg = req.messages[i];
61
- if (msg.role === "user") {
62
- return msg.content
63
- .filter((b) => b.type === "text")
64
- .map((b) => (b as { type: "text"; text: string }).text)
65
- .join("\n");
66
- }
67
- }
68
- return "";
69
- }
70
-
71
- /** Rough token estimate: ~4 characters per token. */
72
- function estimateTokens(text: string): number {
73
- return Math.ceil(text.length / 4);
74
- }
75
-
76
- // ---------------------------------------------------------------------------
77
- // isCompactionRequest
78
- // ---------------------------------------------------------------------------
79
-
80
- /**
81
- * Returns `true` if the request looks like a compaction request.
82
- *
83
- * Checks in order:
84
- * 1. System prompt contains any `COMPACTION_SYSTEM_PATTERNS` → true
85
- * 2. Tools empty AND last user message contains any `COMPACTION_USER_PATTERNS` → true
86
- * 3. Last user message has `<template>` tag AND ≥4 template sections → true
87
- * 4. Otherwise → false
88
- */
89
- export function isCompactionRequest(req: GatewayRequest): boolean {
90
- // 1. System prompt check — strongest signal, sufficient alone
91
- const systemLower = req.system.toLowerCase();
92
- for (const pattern of COMPACTION_SYSTEM_PATTERNS) {
93
- if (systemLower.includes(pattern.toLowerCase())) return true;
94
- }
95
-
96
- const userText = lastUserText(req);
97
-
98
- // 2. No tools + user message contains compaction keywords
99
- if (req.tools.length === 0 && userText) {
100
- for (const pattern of COMPACTION_USER_PATTERNS) {
101
- if (userText.includes(pattern)) return true;
102
- }
103
- }
104
-
105
- // 3. <template> tag + ≥4 section headers
106
- if (userText.includes("<template>")) {
107
- let matches = 0;
108
- for (const section of COMPACTION_TEMPLATE_SECTIONS) {
109
- if (userText.includes(section)) matches++;
110
- }
111
- if (matches >= MIN_TEMPLATE_SECTION_MATCHES) return true;
112
- }
113
-
114
- return false;
115
- }
116
-
117
- // ---------------------------------------------------------------------------
118
- // extractPreviousSummary
119
- // ---------------------------------------------------------------------------
120
-
121
- /** Regex to extract content from `<previous-summary>` block (dotAll). */
122
- const PREVIOUS_SUMMARY_RE =
123
- /<previous-summary>\n(.*?)\n<\/previous-summary>/s;
124
-
125
- /**
126
- * Extract the content of a `<previous-summary>` block from the last user
127
- * message, or `undefined` if no such block exists.
128
- */
129
- export function extractPreviousSummary(
130
- req: GatewayRequest,
131
- ): string | undefined {
132
- const userText = lastUserText(req);
133
- const match = PREVIOUS_SUMMARY_RE.exec(userText);
134
- return match?.[1] ?? undefined;
135
- }
136
-
137
- // ---------------------------------------------------------------------------
138
- // isTitleOrSummaryRequest
139
- // ---------------------------------------------------------------------------
140
-
141
- /** Max system prompt length for title/summary agents (chars). */
142
- const TITLE_SUMMARY_MAX_SYSTEM_LENGTH = 500;
143
-
144
- /** Max number of tools for a title/summary agent (0 or very few). */
145
- const TITLE_SUMMARY_MAX_TOOLS = 2;
146
-
147
- /** Max message count for a title/summary agent (system extracted, so 1–2). */
148
- const TITLE_SUMMARY_MAX_MESSAGES = 2;
149
-
150
- /**
151
- * Detect non-conversation requests that should be forwarded without Lore
152
- * pipeline processing (title generation, summary agents, etc.).
153
- *
154
- * These have:
155
- * - Empty or very few tools (≤2)
156
- * - Only 1–2 messages (system already extracted to `req.system`)
157
- * - Short system prompt (< 500 chars)
158
- * - NOT a compaction request (handled separately)
159
- */
160
- export function isTitleOrSummaryRequest(req: GatewayRequest): boolean {
161
- // Compaction requests are handled separately — don't classify as title/summary
162
- if (isCompactionRequest(req)) return false;
163
-
164
- return (
165
- req.tools.length <= TITLE_SUMMARY_MAX_TOOLS &&
166
- req.messages.length <= TITLE_SUMMARY_MAX_MESSAGES &&
167
- req.system.length < TITLE_SUMMARY_MAX_SYSTEM_LENGTH
168
- );
169
- }
170
-
171
- // ---------------------------------------------------------------------------
172
- // buildCompactionResponse
173
- // ---------------------------------------------------------------------------
174
-
175
- /**
176
- * Build a `GatewayResponse` wrapping a compaction summary as if it were a
177
- * normal assistant response. The gateway translates this back to the
178
- * client's protocol (Anthropic/OpenAI) before sending.
179
- */
180
- export function buildCompactionResponse(
181
- _sessionID: string,
182
- summary: string,
183
- model: string,
184
- ): GatewayResponse {
185
- return {
186
- id: `msg_lore_compact_${crypto.randomUUID().slice(0, 8)}`,
187
- model,
188
- content: [{ type: "text", text: summary }],
189
- stopReason: "end_turn",
190
- usage: {
191
- inputTokens: 0,
192
- outputTokens: estimateTokens(summary),
193
- },
194
- };
195
- }
package/src/config.ts DELETED
@@ -1,199 +0,0 @@
1
- /**
2
- * Gateway configuration — loaded from environment variables with sensible
3
- * defaults. No Zod, no file-based config, no @loreai/core dependency.
4
- */
5
-
6
- // ---------------------------------------------------------------------------
7
- // Config shape
8
- // ---------------------------------------------------------------------------
9
-
10
- export interface GatewayConfig {
11
- /** Port to listen on. Default: 6969. Env: LORE_LISTEN_PORT */
12
- port: number;
13
- /** Host to bind to. Default: "127.0.0.1". Env: LORE_LISTEN_HOST */
14
- host: string;
15
- /** Upstream Anthropic API URL. Default: "https://api.anthropic.com". Env: LORE_UPSTREAM_ANTHROPIC */
16
- upstreamAnthropic: string;
17
- /** Upstream OpenAI API URL. Default: "https://api.openai.com". Env: LORE_UPSTREAM_OPENAI */
18
- upstreamOpenAI: string;
19
- /** Idle timeout in seconds before triggering background work. Default: 60 */
20
- idleTimeoutSeconds: number;
21
- /** Whether to log requests. Default: false. Env: LORE_DEBUG */
22
- debug: boolean;
23
- }
24
-
25
- // ---------------------------------------------------------------------------
26
- // loadConfig
27
- // ---------------------------------------------------------------------------
28
-
29
- /** Load gateway configuration from environment variables with defaults. */
30
- export function loadConfig(): GatewayConfig {
31
- const env = process.env;
32
- return {
33
- port: parsePort(env.LORE_LISTEN_PORT, 6969),
34
- host: env.LORE_LISTEN_HOST || "127.0.0.1",
35
- upstreamAnthropic: trimTrailingSlash(
36
- env.LORE_UPSTREAM_ANTHROPIC || "https://api.anthropic.com",
37
- ),
38
- upstreamOpenAI: trimTrailingSlash(
39
- env.LORE_UPSTREAM_OPENAI || "https://api.openai.com",
40
- ),
41
- idleTimeoutSeconds: parsePositiveInt(env.LORE_IDLE_TIMEOUT, 60),
42
- debug: isTruthy(env.LORE_DEBUG),
43
- };
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Upstream routing — model name → provider URL + protocol
48
- // ---------------------------------------------------------------------------
49
-
50
- export type UpstreamRoute = {
51
- url: string;
52
- protocol: "anthropic" | "openai";
53
- };
54
-
55
- /**
56
- * Model prefix → upstream provider routing table.
57
- *
58
- * Ordered from most-specific to most-general so that e.g. `claude-3-5-haiku`
59
- * matches `claude-` before any catch-all. Unknown models fall back to the
60
- * env-var-configured defaults.
61
- */
62
- const UPSTREAM_ROUTES: Array<{ prefix: string; url: string; protocol: "anthropic" | "openai" }> = [
63
- // Anthropic
64
- { prefix: "claude-", url: "https://api.anthropic.com", protocol: "anthropic" },
65
- // Nvidia NIM
66
- { prefix: "nvidia/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
67
- { prefix: "meta/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
68
- { prefix: "mistralai/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
69
- { prefix: "google/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
70
- { prefix: "qwen/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
71
- { prefix: "deepseek/", url: "https://integrate.api.nvidia.com", protocol: "openai" },
72
- // OpenAI
73
- { prefix: "gpt-", url: "https://api.openai.com", protocol: "openai" },
74
- { prefix: "o1-", url: "https://api.openai.com", protocol: "openai" },
75
- { prefix: "o3-", url: "https://api.openai.com", protocol: "openai" },
76
- { prefix: "o4-", url: "https://api.openai.com", protocol: "openai" },
77
- // xAI
78
- { prefix: "grok-", url: "https://api.x.ai", protocol: "openai" },
79
- // Mistral (direct)
80
- { prefix: "mistral-", url: "https://api.mistral.ai", protocol: "openai" },
81
- { prefix: "codestral-", url: "https://api.mistral.ai", protocol: "openai" },
82
- // Google (direct)
83
- { prefix: "gemini-", url: "https://generativelanguage.googleapis.com", protocol: "openai" },
84
- ];
85
-
86
- /**
87
- * Resolve which upstream to use for a given model name.
88
- *
89
- * Returns the inferred route, or null if the model doesn't match any known
90
- * prefix (caller should fall back to env-var-configured defaults).
91
- */
92
- export function resolveUpstreamRoute(model: string): UpstreamRoute | null {
93
- for (const route of UPSTREAM_ROUTES) {
94
- if (model.startsWith(route.prefix)) {
95
- return { url: route.url, protocol: route.protocol };
96
- }
97
- }
98
- return null;
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
- // Project path inference
103
- // ---------------------------------------------------------------------------
104
-
105
- /**
106
- * Regex patterns to extract an absolute project path from a system prompt.
107
- *
108
- * Claude Code embeds absolute paths in several places:
109
- * - CLAUDE.md content references (`/home/user/project/CLAUDE.md`)
110
- * - Tool definitions mention cwd (`"cwd": "/home/user/project"`)
111
- * - Working directory lines (`Working directory: /Users/…/project`)
112
- *
113
- * Each pattern captures the directory portion (no trailing filename when
114
- * possible). Ordered from most-specific to most-general.
115
- */
116
- const PROJECT_PATH_PATTERNS: RegExp[] = [
117
- // "cwd": "/home/…/project" or "cwd":"/Users/…/project" (JSON-style)
118
- /["']?cwd["']?\s*[:=]\s*["']?(\/(?:home|Users)\/[^\s"',}]+)/,
119
- // Working directory: /home/user/project
120
- /[Ww]orking\s+directory[:=]\s*(\/(?:home|Users)\/[^\s"',]+)/,
121
- // CLAUDE.md / AGENTS.md / .lore.md file path → take the directory
122
- /(\/(?:home|Users)\/[^\s"',]+)\/(?:CLAUDE|AGENTS|\.lore)\.md/,
123
- // Generic absolute path starting with /home/ or /Users/ — first occurrence
124
- // Captures until whitespace, quote, comma, or bracket.
125
- /(\/(?:home|Users)\/[\w./-]+)/,
126
- ];
127
-
128
- /**
129
- * Try to extract a project path from the system prompt content.
130
- *
131
- * Claude Code includes absolute paths in its system prompt (CLAUDE.md
132
- * content, tool definitions, working directory references). Returns the
133
- * extracted path or `null` if nothing looks like a project directory.
134
- */
135
- export function inferProjectPath(systemPrompt: string): string | null {
136
- for (const pattern of PROJECT_PATH_PATTERNS) {
137
- const match = pattern.exec(systemPrompt);
138
- if (match?.[1]) {
139
- // Strip trailing slashes for consistency
140
- return match[1].replace(/\/+$/, "") || null;
141
- }
142
- }
143
- return null;
144
- }
145
-
146
- // ---------------------------------------------------------------------------
147
- // getProjectPath
148
- // ---------------------------------------------------------------------------
149
-
150
- /**
151
- * Resolve the project path for a request. Checks in order:
152
- * 1. `X-Lore-Project` header (explicit override)
153
- * 2. `inferProjectPath(systemPrompt)` (zero-config extraction)
154
- * 3. `process.cwd()` (last resort fallback)
155
- */
156
- export function getProjectPath(
157
- systemPrompt: string,
158
- headers: Record<string, string>,
159
- ): string {
160
- // 1. Explicit header override
161
- const headerPath = headers["x-lore-project"];
162
- if (headerPath) return headerPath;
163
-
164
- // 2. Infer from system prompt content
165
- const inferred = inferProjectPath(systemPrompt);
166
- if (inferred) return inferred;
167
-
168
- // 3. Fall back to gateway's own cwd
169
- return process.cwd();
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // Helpers (not exported — internal only)
174
- // ---------------------------------------------------------------------------
175
-
176
- function parsePort(value: string | undefined, fallback: number): number {
177
- if (!value) return fallback;
178
- const n = Number.parseInt(value, 10);
179
- if (Number.isNaN(n) || n < 0 || n > 65535) return fallback;
180
- return n;
181
- }
182
-
183
- function parsePositiveInt(
184
- value: string | undefined,
185
- fallback: number,
186
- ): number {
187
- if (!value) return fallback;
188
- const n = Number.parseInt(value, 10);
189
- if (Number.isNaN(n) || n <= 0) return fallback;
190
- return n;
191
- }
192
-
193
- function isTruthy(value: string | undefined): boolean {
194
- return value === "1" || value?.toLowerCase() === "true";
195
- }
196
-
197
- function trimTrailingSlash(url: string): string {
198
- return url.replace(/\/+$/, "");
199
- }
package/src/idle.ts DELETED
@@ -1,240 +0,0 @@
1
- /**
2
- * Idle detection and background work scheduling for the Lore gateway.
3
- *
4
- * Since the gateway doesn't have host lifecycle hooks (like OpenCode's
5
- * `session.idle` event), it uses a timer-based approach to detect when
6
- * sessions go idle and trigger background work (distillation, curation,
7
- * pruning, AGENTS.md export, etc.).
8
- */
9
-
10
- import { join } from "node:path";
11
- import {
12
- temporal,
13
- distillation,
14
- curator,
15
- ltm,
16
- latReader,
17
- log,
18
- config as loreConfig,
19
- getLastTurnAt,
20
- exportToFile,
21
- exportLoreFile,
22
- } from "@loreai/core";
23
- import type { LLMClient } from "@loreai/core";
24
- import type { GatewayConfig } from "./config";
25
- import type { SessionState } from "./translate/types";
26
- import type { AuthCredential } from "./auth";
27
- import { maybeValidateWorkerModel, getWorkerModel } from "./worker-model";
28
-
29
- const POLL_INTERVAL_MS = 30_000;
30
-
31
- // ---------------------------------------------------------------------------
32
- // startIdleScheduler
33
- // ---------------------------------------------------------------------------
34
-
35
- /**
36
- * Start a periodic timer that checks each active session for idle timeout.
37
- *
38
- * Every 30 seconds, walks the sessions map and fires `doIdleWork` for any
39
- * session whose `lastRequestTime` is older than `config.idleTimeoutSeconds`.
40
- * Tracks in-progress sessions to avoid double-triggering.
41
- *
42
- * @returns A cleanup function that clears the interval timer.
43
- */
44
- export function startIdleScheduler(
45
- config: GatewayConfig,
46
- sessions: Map<string, SessionState>,
47
- doIdleWork: (sessionID: string, state: SessionState) => Promise<void>,
48
- ): () => void {
49
- const inProgress = new Set<string>();
50
-
51
- const timer = setInterval(() => {
52
- const now = Date.now();
53
- const timeoutMs = config.idleTimeoutSeconds * 1000;
54
-
55
- for (const [sessionID, state] of sessions) {
56
- if (inProgress.has(sessionID)) continue;
57
- if (now - state.lastRequestTime < timeoutMs) continue;
58
-
59
- inProgress.add(sessionID);
60
- doIdleWork(sessionID, state)
61
- .catch((e) => log.error(`idle work failed for session ${sessionID}:`, e))
62
- .finally(() => inProgress.delete(sessionID));
63
- }
64
- }, POLL_INTERVAL_MS);
65
-
66
- return () => clearInterval(timer);
67
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // touchSession
71
- // ---------------------------------------------------------------------------
72
-
73
- /**
74
- * Update a session's `lastRequestTime` to now. Called on every request.
75
- */
76
- export function touchSession(
77
- sessions: Map<string, SessionState>,
78
- sessionID: string,
79
- ): void {
80
- const state = sessions.get(sessionID);
81
- if (state) {
82
- state.lastRequestTime = Date.now();
83
- }
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // buildIdleWorkHandler
88
- // ---------------------------------------------------------------------------
89
-
90
- /**
91
- * Build the idle work handler that runs the same background tasks as
92
- * OpenCode/Pi adapters on session idle:
93
- *
94
- * 1. Distillation (if enough undistilled messages)
95
- * 2. Curation (if enough turns since last curation)
96
- * 3. Consolidation (if entries exceed max)
97
- * 4. Temporal pruning
98
- * 5. AGENTS.md export
99
- * 6. Dead reference cleanup
100
- * 7. lat.md refresh
101
- *
102
- * Each step is independently try/catch'd — one failure won't block the rest.
103
- *
104
- * @param projectPath - Resolved project directory path
105
- * @param llm - LLM client for worker calls (distillation, curation)
106
- * @param upstreamUrl - Anthropic API base URL (for model discovery)
107
- * @param getAuth - Callback to resolve auth credentials
108
- * @param sessionModel - Model ID used for conversation (frontier model)
109
- */
110
- export function buildIdleWorkHandler(
111
- projectPath: string,
112
- llm: LLMClient,
113
- upstreamUrl: string,
114
- getAuth: () => AuthCredential | null,
115
- sessionModel: string,
116
- ): (sessionID: string, state: SessionState) => Promise<void> {
117
- return async (sessionID: string, state: SessionState) => {
118
- const cfg = loreConfig();
119
-
120
- // 0. Worker model validation — discover cheaper models for background work.
121
- // Runs before distillation/curation so the resolved model is up-to-date.
122
- try {
123
- const cred = getAuth();
124
- if (cred) {
125
- await maybeValidateWorkerModel(
126
- sessionModel,
127
- upstreamUrl,
128
- cred,
129
- llm,
130
- projectPath,
131
- sessionID,
132
- );
133
- }
134
- } catch (e) {
135
- log.error("idle worker model validation error:", e);
136
- }
137
-
138
- const model = getWorkerModel();
139
-
140
- // 1. Distillation — force-distill ALL pending messages on idle, even
141
- // below minMessages. The cache is going cold; aggressive distillation
142
- // now means a smaller context on the next turn via post-idle compact.
143
- // Meta-distillation is always allowed on idle (cache is cold anyway).
144
- try {
145
- const pending = temporal.undistilledCount(projectPath, sessionID);
146
- if (pending > 0) {
147
- await distillation.run({ llm, projectPath, sessionID, model, force: true });
148
- }
149
- } catch (e) {
150
- log.error("idle distillation error:", e);
151
- }
152
-
153
- // 2. Curation
154
- if (cfg.knowledge.enabled && cfg.curator.onIdle) {
155
- try {
156
- if (state.turnsSinceCuration >= cfg.curator.afterTurns) {
157
- await curator.run({ llm, projectPath, sessionID, model });
158
- state.turnsSinceCuration = 0;
159
- }
160
- } catch (e) {
161
- log.error("idle curation error:", e);
162
- }
163
- }
164
-
165
- // 3. Consolidation — runs after curation so new entries are counted
166
- if (cfg.knowledge.enabled) {
167
- try {
168
- const entries = ltm.forProject(projectPath, false);
169
- if (entries.length > cfg.curator.maxEntries) {
170
- log.info(
171
- `entry count ${entries.length} exceeds maxEntries ${cfg.curator.maxEntries} — running consolidation`,
172
- );
173
- const { updated, deleted } = await curator.consolidate({
174
- llm,
175
- projectPath,
176
- sessionID,
177
- model,
178
- });
179
- if (updated > 0 || deleted > 0) {
180
- log.info(`consolidation: ${updated} updated, ${deleted} deleted`);
181
- }
182
- }
183
- } catch (e) {
184
- log.error("idle consolidation error:", e);
185
- }
186
- }
187
-
188
- // 4. Temporal pruning
189
- try {
190
- const { ttlDeleted, capDeleted } = temporal.prune({
191
- projectPath,
192
- retentionDays: cfg.pruning.retention,
193
- maxStorageMB: cfg.pruning.maxStorage,
194
- });
195
- if (ttlDeleted > 0 || capDeleted > 0) {
196
- log.info(
197
- `pruned temporal messages: ${ttlDeleted} by TTL, ${capDeleted} by size cap`,
198
- );
199
- }
200
- } catch (e) {
201
- log.error("idle pruning error:", e);
202
- }
203
-
204
- // 5. Knowledge export (.lore.md + optional agents file pointer)
205
- if (cfg.knowledge.enabled) {
206
- try {
207
- const entries = ltm.forProject(projectPath, false);
208
- if (entries.length > 0) {
209
- if (cfg.agentsFile.enabled) {
210
- const filePath = join(projectPath, cfg.agentsFile.path);
211
- exportToFile({ projectPath, filePath });
212
- } else {
213
- exportLoreFile(projectPath);
214
- }
215
- }
216
- } catch (e) {
217
- log.error("idle knowledge export error:", e);
218
- }
219
- }
220
-
221
- // 6. Dead reference cleanup
222
- if (cfg.knowledge.enabled) {
223
- try {
224
- const cleaned = ltm.cleanDeadRefs();
225
- if (cleaned > 0) {
226
- log.info(`cleaned ${cleaned} dead knowledge cross-references`);
227
- }
228
- } catch (e) {
229
- log.error("idle dead-ref cleanup error:", e);
230
- }
231
- }
232
-
233
- // 7. lat.md refresh
234
- try {
235
- latReader.refresh(projectPath);
236
- } catch (e) {
237
- log.error("idle lat-reader refresh error:", e);
238
- }
239
- };
240
- }
package/src/index.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * Lore Gateway — package entry point.
3
- *
4
- * Library exports for programmatic use, plus `_cli()` for the CLI binary.
5
- *
6
- * Library usage:
7
- * import { startServer, loadConfig } from "@loreai/gateway";
8
- *
9
- * CLI usage (via bin wrapper):
10
- * lore start
11
- * lore run claude
12
- */
13
- import "../instrument";
14
-
15
- // ---------------------------------------------------------------------------
16
- // Library API
17
- // ---------------------------------------------------------------------------
18
-
19
- export { loadConfig } from "./config";
20
- export type { GatewayConfig } from "./config";
21
- export { startServer } from "./server";
22
- export { handleRequest, resetPipelineState } from "./pipeline";
23
-
24
- // ---------------------------------------------------------------------------
25
- // CLI entry — called by dist/bin.cjs or `bun run src/index.ts`
26
- // ---------------------------------------------------------------------------
27
-
28
- export { _cli } from "./cli/main";
29
-
30
- // ---------------------------------------------------------------------------
31
- // Direct execution — `bun run src/index.ts` still works as before
32
- // ---------------------------------------------------------------------------
33
-
34
- if (typeof Bun !== "undefined" && Bun.main === import.meta.path) {
35
- // Direct execution (e.g. `bun run src/index.ts` from the OpenCode plugin)
36
- // defaults to server-only mode (`start`), not `run` — there's no TTY and
37
- // no reason to auto-detect agents when launched as an embedded server.
38
- // esbuild CJS output drops import.meta to `{}` so the condition is
39
- // always false in the npm bundle — the await is dead-code-eliminated.
40
- import("./cli/start").then(({ commandStart }) => commandStart({}));
41
- }