teleton 0.5.2 → 0.7.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 +243 -105
- package/dist/chunk-FNV5FF35.js +331 -0
- package/dist/chunk-LRCPA7SC.js +149 -0
- package/dist/{chunk-WUTMT6DW.js → chunk-N3F7E7DR.js} +114 -566
- package/dist/chunk-ND2X5FWB.js +368 -0
- package/dist/chunk-NERLQY2H.js +421 -0
- package/dist/{chunk-YBA6IBGT.js → chunk-OCLG5GKI.js} +24 -24
- package/dist/{chunk-WOXBZOQX.js → chunk-OGIG552S.js} +2152 -4488
- package/dist/chunk-RCMD3U65.js +141 -0
- package/dist/{chunk-O4R7V5Y2.js → chunk-RO62LO6Z.js} +11 -1
- package/dist/chunk-TCD4NZDA.js +3226 -0
- package/dist/chunk-UCN6TI25.js +143 -0
- package/dist/{chunk-WL2Q3VRD.js → chunk-UDD7FYOU.js} +12 -4
- package/dist/chunk-VAUJSSD3.js +20 -0
- package/dist/chunk-XBE4JB7C.js +8 -0
- package/dist/chunk-XBKSS6DM.js +58 -0
- package/dist/cli/index.js +1179 -412
- package/dist/client-3VWE7NC4.js +29 -0
- package/dist/{get-my-gifts-KVULMBJ3.js → get-my-gifts-RI7FAXAL.js} +3 -1
- package/dist/index.js +17 -8
- package/dist/{memory-Y5J7CXAR.js → memory-RD7ZSTRV.js} +16 -10
- package/dist/{migrate-UEQCDWL2.js → migrate-GO4NOBT7.js} +14 -6
- package/dist/{server-BQY7CM2N.js → server-OWVEZTR3.js} +869 -93
- package/dist/setup-server-C7ZTPHD5.js +934 -0
- package/dist/{task-dependency-resolver-TRPILAHM.js → task-dependency-resolver-WKZWJLLM.js} +19 -15
- package/dist/{task-executor-N7XNVK5N.js → task-executor-PD3H4MLO.js} +5 -2
- package/dist/tool-adapter-Y3TCEQOC.js +145 -0
- package/dist/tool-index-MIVK3D7H.js +250 -0
- package/dist/{transcript-7V4UNID4.js → transcript-UDJZP6NK.js} +2 -1
- package/dist/web/assets/complete-fZLnb5Ot.js +1 -0
- package/dist/web/assets/index-B_FcaX5D.css +1 -0
- package/dist/web/assets/index-CbeAP4_n.js +67 -0
- package/dist/web/assets/index.es-oXiZF7Hc.js +11 -0
- package/dist/web/assets/login-telegram-BP7CJDmx.js +1 -0
- package/dist/web/assets/run-DOrDowjK.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +21 -15
- package/dist/chunk-5WWR4CU3.js +0 -124
- package/dist/web/assets/index-CDMbujHf.css +0 -1
- package/dist/web/assets/index-DDX8oQ2z.js +0 -67
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_DEPENDENTS_PER_TASK
|
|
3
|
+
} from "./chunk-RO62LO6Z.js";
|
|
1
4
|
import {
|
|
2
5
|
BATCH_TRIGGER_DELAY_MS
|
|
3
6
|
} from "./chunk-4DU3C27M.js";
|
|
4
7
|
import {
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
createLogger
|
|
9
|
+
} from "./chunk-RCMD3U65.js";
|
|
7
10
|
import "./chunk-QGM4M3NI.js";
|
|
8
11
|
|
|
9
12
|
// src/telegram/task-dependency-resolver.ts
|
|
13
|
+
var log = createLogger("Telegram");
|
|
10
14
|
var TaskDependencyResolver = class {
|
|
11
15
|
constructor(taskStore, bridge) {
|
|
12
16
|
this.taskStore = taskStore;
|
|
@@ -28,25 +32,25 @@ var TaskDependencyResolver = class {
|
|
|
28
32
|
const dependentIds = allDependentIds.slice(0, MAX_DEPENDENTS_PER_TASK);
|
|
29
33
|
const truncated = allDependentIds.length > MAX_DEPENDENTS_PER_TASK;
|
|
30
34
|
if (truncated) {
|
|
31
|
-
|
|
35
|
+
log.warn(
|
|
32
36
|
`\u26A0\uFE0F Task ${completedTaskId} has ${allDependentIds.length} dependents, only processing first ${MAX_DEPENDENTS_PER_TASK} (security limit)`
|
|
33
37
|
);
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
log.info(
|
|
36
40
|
`\u{1F4CA} Task ${completedTaskId} completed. Checking ${dependentIds.length} dependent task(s)...`
|
|
37
41
|
);
|
|
38
42
|
const tasksToTrigger = [];
|
|
39
43
|
for (const depId of dependentIds) {
|
|
40
44
|
const task = this.taskStore.getTask(depId);
|
|
41
45
|
if (!task) {
|
|
42
|
-
|
|
46
|
+
log.warn(`Dependent task ${depId} not found`);
|
|
43
47
|
continue;
|
|
44
48
|
}
|
|
45
49
|
if (task.status !== "pending") {
|
|
46
50
|
continue;
|
|
47
51
|
}
|
|
48
52
|
if (!this.taskStore.canExecute(depId)) {
|
|
49
|
-
|
|
53
|
+
log.info(`\u23F3 Task ${depId} still waiting for dependencies`);
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
52
56
|
tasksToTrigger.push(task.id);
|
|
@@ -58,7 +62,7 @@ var TaskDependencyResolver = class {
|
|
|
58
62
|
await this.triggerTask(tasksToTrigger[i]);
|
|
59
63
|
}
|
|
60
64
|
} catch (error) {
|
|
61
|
-
|
|
65
|
+
log.error({ err: error }, "Error in dependency resolver");
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
@@ -83,11 +87,11 @@ var TaskDependencyResolver = class {
|
|
|
83
87
|
const dependentIds = allDependentIds.slice(0, MAX_DEPENDENTS_PER_TASK);
|
|
84
88
|
const truncated = allDependentIds.length > MAX_DEPENDENTS_PER_TASK;
|
|
85
89
|
if (truncated) {
|
|
86
|
-
|
|
90
|
+
log.warn(
|
|
87
91
|
`\u26A0\uFE0F Task ${failedTaskId} has ${allDependentIds.length} dependents, only cancelling first ${MAX_DEPENDENTS_PER_TASK} (security limit)`
|
|
88
92
|
);
|
|
89
93
|
}
|
|
90
|
-
|
|
94
|
+
log.info(
|
|
91
95
|
`\u274C Task ${failedTaskId} failed. Cancelling ${dependentIds.length} dependent task(s)...`
|
|
92
96
|
);
|
|
93
97
|
for (const depId of dependentIds) {
|
|
@@ -105,12 +109,12 @@ var TaskDependencyResolver = class {
|
|
|
105
109
|
}
|
|
106
110
|
if (skipOnFailure) {
|
|
107
111
|
this.taskStore.cancelTask(depId);
|
|
108
|
-
|
|
112
|
+
log.info(`\u21B3 Cancelled task ${depId}: ${task.description}`);
|
|
109
113
|
await this.onTaskFail(depId);
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
116
|
} catch (error) {
|
|
113
|
-
|
|
117
|
+
log.error({ err: error }, "Error handling task failure cascade");
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
/**
|
|
@@ -120,18 +124,18 @@ var TaskDependencyResolver = class {
|
|
|
120
124
|
try {
|
|
121
125
|
const task = this.taskStore.getTask(taskId);
|
|
122
126
|
if (!task) {
|
|
123
|
-
|
|
127
|
+
log.warn(`Cannot trigger task ${taskId}: not found`);
|
|
124
128
|
return;
|
|
125
129
|
}
|
|
126
|
-
|
|
130
|
+
log.info(`\u{1F680} Triggering dependent task: ${task.description}`);
|
|
127
131
|
const gramJsClient = this.bridge.getClient().getClient();
|
|
128
132
|
const me = await gramJsClient.getMe();
|
|
129
133
|
await gramJsClient.sendMessage(me, {
|
|
130
134
|
message: `[TASK:${taskId}] ${task.description}`
|
|
131
135
|
});
|
|
132
|
-
|
|
136
|
+
log.info(`\u21B3 Sent [TASK:${taskId}] to Saved Messages`);
|
|
133
137
|
} catch (error) {
|
|
134
|
-
|
|
138
|
+
log.error({ err: error }, `Error triggering task ${taskId}`);
|
|
135
139
|
this.taskStore.failTask(taskId, `Failed to trigger: ${error}`);
|
|
136
140
|
}
|
|
137
141
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getErrorMessage
|
|
3
|
+
} from "./chunk-XBE4JB7C.js";
|
|
1
4
|
import {
|
|
2
5
|
MAX_JSON_FIELD_CHARS,
|
|
3
6
|
MAX_TOTAL_PROMPT_CHARS,
|
|
4
7
|
SECONDS_PER_DAY,
|
|
5
8
|
SECONDS_PER_HOUR
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RO62LO6Z.js";
|
|
7
10
|
import "./chunk-QGM4M3NI.js";
|
|
8
11
|
|
|
9
12
|
// src/telegram/task-executor.ts
|
|
@@ -42,7 +45,7 @@ async function executeScheduledTask(task, agent, toolContext, toolRegistry, pare
|
|
|
42
45
|
{
|
|
43
46
|
toolExecuted: payload.tool,
|
|
44
47
|
toolParams: payload.params,
|
|
45
|
-
toolError:
|
|
48
|
+
toolError: getErrorMessage(error)
|
|
46
49
|
},
|
|
47
50
|
parentResults
|
|
48
51
|
);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLogger
|
|
3
|
+
} from "./chunk-RCMD3U65.js";
|
|
4
|
+
import "./chunk-QGM4M3NI.js";
|
|
5
|
+
|
|
6
|
+
// src/cocoon/tool-adapter.ts
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
var log = createLogger("Cocoon");
|
|
9
|
+
var TOOL_PREAMBLE = `
|
|
10
|
+
|
|
11
|
+
# Tools
|
|
12
|
+
|
|
13
|
+
You may call one or more functions to assist with the user query.
|
|
14
|
+
|
|
15
|
+
You are provided with function signatures within <tools></tools> XML tags:
|
|
16
|
+
<tools>
|
|
17
|
+
`;
|
|
18
|
+
var TOOL_POSTAMBLE = `</tools>
|
|
19
|
+
|
|
20
|
+
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
|
|
21
|
+
<tool_call>
|
|
22
|
+
{"name": <function-name>, "arguments": <args-json-object>}
|
|
23
|
+
</tool_call>`;
|
|
24
|
+
function injectToolsIntoSystemPrompt(systemPrompt, tools) {
|
|
25
|
+
if (!tools || tools.length === 0) return systemPrompt;
|
|
26
|
+
const toolLines = tools.map(
|
|
27
|
+
(t) => JSON.stringify({
|
|
28
|
+
type: "function",
|
|
29
|
+
function: {
|
|
30
|
+
name: t.name,
|
|
31
|
+
description: t.description,
|
|
32
|
+
parameters: t.parameters
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
return systemPrompt + TOOL_PREAMBLE + toolLines.join("\n") + "\n" + TOOL_POSTAMBLE;
|
|
37
|
+
}
|
|
38
|
+
var UNSUPPORTED_FIELDS = ["tools", "tool_choice", "store", "reasoning_effort", "stream_options"];
|
|
39
|
+
function stripCocoonPayload(payload) {
|
|
40
|
+
if (typeof payload !== "object" || payload === null) return;
|
|
41
|
+
const obj = payload;
|
|
42
|
+
for (const field of UNSUPPORTED_FIELDS) {
|
|
43
|
+
delete obj[field];
|
|
44
|
+
}
|
|
45
|
+
obj.presence_penalty = obj.presence_penalty ?? 1.5;
|
|
46
|
+
}
|
|
47
|
+
var TOOL_CALL_OPEN = "<tool_call>";
|
|
48
|
+
var TOOL_CALL_CLOSE = "</tool_call>";
|
|
49
|
+
var THINK_RE = /<think>[\s\S]*?<\/think>/g;
|
|
50
|
+
function extractJsonObject(text, startIndex) {
|
|
51
|
+
let braceCount = 0;
|
|
52
|
+
let inString = false;
|
|
53
|
+
let escaped = false;
|
|
54
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
55
|
+
const ch = text[i];
|
|
56
|
+
if (escaped) {
|
|
57
|
+
escaped = false;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ch === "\\" && inString) {
|
|
61
|
+
escaped = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '"') {
|
|
65
|
+
inString = !inString;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!inString) {
|
|
69
|
+
if (ch === "{") braceCount++;
|
|
70
|
+
if (ch === "}") {
|
|
71
|
+
braceCount--;
|
|
72
|
+
if (braceCount === 0) {
|
|
73
|
+
return { json: text.slice(startIndex, i + 1), endIndex: i };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function parseToolCallsFromText(text, allowedTools) {
|
|
81
|
+
const cleaned = text.replace(THINK_RE, "").trim();
|
|
82
|
+
const calls = [];
|
|
83
|
+
let searchFrom = 0;
|
|
84
|
+
while (true) {
|
|
85
|
+
const openIdx = cleaned.indexOf(TOOL_CALL_OPEN, searchFrom);
|
|
86
|
+
if (openIdx === -1) break;
|
|
87
|
+
const contentStart = openIdx + TOOL_CALL_OPEN.length;
|
|
88
|
+
const closeIdx = cleaned.indexOf(TOOL_CALL_CLOSE, contentStart);
|
|
89
|
+
if (closeIdx === -1) break;
|
|
90
|
+
const braceStart = cleaned.indexOf("{", contentStart);
|
|
91
|
+
if (braceStart === -1 || braceStart >= closeIdx) {
|
|
92
|
+
searchFrom = closeIdx + TOOL_CALL_CLOSE.length;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const extracted = extractJsonObject(cleaned, braceStart);
|
|
96
|
+
if (extracted) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(extracted.json);
|
|
99
|
+
if (parsed.name && typeof parsed.name === "string") {
|
|
100
|
+
if (allowedTools && !allowedTools.has(parsed.name)) {
|
|
101
|
+
log.warn(`Cocoon: rejected tool call "${parsed.name}" \u2014 not in allowed set`);
|
|
102
|
+
} else {
|
|
103
|
+
calls.push({
|
|
104
|
+
type: "toolCall",
|
|
105
|
+
id: `cocoon_${randomUUID()}`,
|
|
106
|
+
name: parsed.name,
|
|
107
|
+
arguments: parsed.arguments ?? {}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
log.debug(`Failed to parse tool call JSON: ${String(e)}`);
|
|
113
|
+
log.debug(`Raw: ${extracted.json.slice(0, 200)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
searchFrom = closeIdx + TOOL_CALL_CLOSE.length;
|
|
117
|
+
}
|
|
118
|
+
return calls;
|
|
119
|
+
}
|
|
120
|
+
function extractPlainText(text) {
|
|
121
|
+
let result = text.replace(THINK_RE, "");
|
|
122
|
+
let searchFrom = 0;
|
|
123
|
+
while (true) {
|
|
124
|
+
const openIdx = result.indexOf(TOOL_CALL_OPEN, searchFrom);
|
|
125
|
+
if (openIdx === -1) break;
|
|
126
|
+
const closeIdx = result.indexOf(TOOL_CALL_CLOSE, openIdx);
|
|
127
|
+
if (closeIdx === -1) break;
|
|
128
|
+
result = result.slice(0, openIdx) + result.slice(closeIdx + TOOL_CALL_CLOSE.length);
|
|
129
|
+
searchFrom = openIdx;
|
|
130
|
+
}
|
|
131
|
+
return result.trim();
|
|
132
|
+
}
|
|
133
|
+
function wrapToolResult(resultText) {
|
|
134
|
+
const safe = resultText.replace(/]]>/g, "]]]]><![CDATA[>");
|
|
135
|
+
return `<tool_response>
|
|
136
|
+
<![CDATA[${safe}]]>
|
|
137
|
+
</tool_response>`;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
extractPlainText,
|
|
141
|
+
injectToolsIntoSystemPrompt,
|
|
142
|
+
parseToolCallsFromText,
|
|
143
|
+
stripCocoonPayload,
|
|
144
|
+
wrapToolResult
|
|
145
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
serializeEmbedding
|
|
3
|
+
} from "./chunk-FNV5FF35.js";
|
|
4
|
+
import "./chunk-XBKSS6DM.js";
|
|
5
|
+
import {
|
|
6
|
+
TOOL_RAG_KEYWORD_WEIGHT,
|
|
7
|
+
TOOL_RAG_MIN_SCORE,
|
|
8
|
+
TOOL_RAG_VECTOR_WEIGHT
|
|
9
|
+
} from "./chunk-RO62LO6Z.js";
|
|
10
|
+
import "./chunk-VAUJSSD3.js";
|
|
11
|
+
import "./chunk-4DU3C27M.js";
|
|
12
|
+
import "./chunk-EYWNOHMJ.js";
|
|
13
|
+
import {
|
|
14
|
+
createLogger
|
|
15
|
+
} from "./chunk-RCMD3U65.js";
|
|
16
|
+
import "./chunk-QGM4M3NI.js";
|
|
17
|
+
|
|
18
|
+
// src/agent/tools/tool-index.ts
|
|
19
|
+
var log = createLogger("ToolRAG");
|
|
20
|
+
function escapeFts5Query(query) {
|
|
21
|
+
return query.replace(/["\*\-\+\(\)\:\^\~\?\.\@\#\$\%\&\!\[\]\{\}\|\\\/<>=,;'`]/g, " ").replace(/\s+/g, " ").trim();
|
|
22
|
+
}
|
|
23
|
+
function bm25ToScore(rank) {
|
|
24
|
+
return 1 / (1 + Math.exp(rank));
|
|
25
|
+
}
|
|
26
|
+
var ToolIndex = class {
|
|
27
|
+
constructor(db, embedder, vectorEnabled, config) {
|
|
28
|
+
this.db = db;
|
|
29
|
+
this.embedder = embedder;
|
|
30
|
+
this.vectorEnabled = vectorEnabled;
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
_isIndexed = false;
|
|
34
|
+
get isIndexed() {
|
|
35
|
+
return this._isIndexed;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create the vector table (dimensions are dynamic, so can't be in schema migration).
|
|
39
|
+
*/
|
|
40
|
+
ensureSchema() {
|
|
41
|
+
if (!this.vectorEnabled || this.embedder.dimensions === 0) return;
|
|
42
|
+
try {
|
|
43
|
+
const existing = this.db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='tool_index_vec'`).get();
|
|
44
|
+
if (existing?.sql && !existing.sql.includes(`[${this.embedder.dimensions}]`)) {
|
|
45
|
+
this.db.exec(`DROP TABLE IF EXISTS tool_index_vec`);
|
|
46
|
+
}
|
|
47
|
+
this.db.exec(`
|
|
48
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tool_index_vec USING vec0(
|
|
49
|
+
name TEXT PRIMARY KEY,
|
|
50
|
+
embedding FLOAT[${this.embedder.dimensions}] distance_metric=cosine
|
|
51
|
+
);
|
|
52
|
+
`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
log.error({ err: error }, "Failed to create vector table");
|
|
55
|
+
this.vectorEnabled = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Index all registered tools. Replaces any previous index.
|
|
60
|
+
*/
|
|
61
|
+
async indexAll(tools) {
|
|
62
|
+
try {
|
|
63
|
+
this.db.exec(`DELETE FROM tool_index`);
|
|
64
|
+
if (this.vectorEnabled) {
|
|
65
|
+
try {
|
|
66
|
+
this.db.exec(`DELETE FROM tool_index_vec`);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const entries = tools.map((t) => ({
|
|
71
|
+
name: t.name,
|
|
72
|
+
description: t.description ?? "",
|
|
73
|
+
searchText: `${t.name} \u2014 ${t.description ?? ""}`
|
|
74
|
+
}));
|
|
75
|
+
const embeddings = [];
|
|
76
|
+
if (this.vectorEnabled && this.embedder.dimensions > 0) {
|
|
77
|
+
const texts = entries.map((e) => e.searchText);
|
|
78
|
+
const batchSize = 128;
|
|
79
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
80
|
+
const batch = texts.slice(i, i + batchSize);
|
|
81
|
+
const batchEmbeddings = await this.embedder.embedBatch(batch);
|
|
82
|
+
embeddings.push(...batchEmbeddings);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const insertTool = this.db.prepare(`
|
|
86
|
+
INSERT INTO tool_index (name, description, search_text, updated_at)
|
|
87
|
+
VALUES (?, ?, ?, unixepoch())
|
|
88
|
+
`);
|
|
89
|
+
const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO tool_index_vec (name, embedding) VALUES (?, ?)`) : null;
|
|
90
|
+
const txn = this.db.transaction(() => {
|
|
91
|
+
for (let i = 0; i < entries.length; i++) {
|
|
92
|
+
const e = entries[i];
|
|
93
|
+
insertTool.run(e.name, e.description, e.searchText);
|
|
94
|
+
if (insertVec && embeddings[i]?.length > 0) {
|
|
95
|
+
insertVec.run(e.name, serializeEmbedding(embeddings[i]));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
txn();
|
|
100
|
+
this._isIndexed = true;
|
|
101
|
+
return entries.length;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
log.error({ err: error }, "Indexing failed");
|
|
104
|
+
this._isIndexed = false;
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Delta update for hot-reload plugins.
|
|
110
|
+
*/
|
|
111
|
+
async reindexTools(removed, added) {
|
|
112
|
+
try {
|
|
113
|
+
if (removed.length > 0) {
|
|
114
|
+
const deleteTool = this.db.prepare(`DELETE FROM tool_index WHERE name = ?`);
|
|
115
|
+
const deleteVec = this.vectorEnabled ? this.db.prepare(`DELETE FROM tool_index_vec WHERE name = ?`) : null;
|
|
116
|
+
for (const name of removed) {
|
|
117
|
+
deleteTool.run(name);
|
|
118
|
+
deleteVec?.run(name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (added.length > 0) {
|
|
122
|
+
const entries = added.map((t) => ({
|
|
123
|
+
name: t.name,
|
|
124
|
+
description: t.description ?? "",
|
|
125
|
+
searchText: `${t.name} \u2014 ${t.description ?? ""}`
|
|
126
|
+
}));
|
|
127
|
+
let embeddings = [];
|
|
128
|
+
if (this.vectorEnabled && this.embedder.dimensions > 0) {
|
|
129
|
+
embeddings = await this.embedder.embedBatch(entries.map((e) => e.searchText));
|
|
130
|
+
}
|
|
131
|
+
const insertTool = this.db.prepare(`
|
|
132
|
+
INSERT OR REPLACE INTO tool_index (name, description, search_text, updated_at)
|
|
133
|
+
VALUES (?, ?, ?, unixepoch())
|
|
134
|
+
`);
|
|
135
|
+
const deleteVec = this.vectorEnabled ? this.db.prepare(`DELETE FROM tool_index_vec WHERE name = ?`) : null;
|
|
136
|
+
const insertVec = this.vectorEnabled ? this.db.prepare(`INSERT INTO tool_index_vec (name, embedding) VALUES (?, ?)`) : null;
|
|
137
|
+
const txn = this.db.transaction(() => {
|
|
138
|
+
for (let i = 0; i < entries.length; i++) {
|
|
139
|
+
const e = entries[i];
|
|
140
|
+
insertTool.run(e.name, e.description, e.searchText);
|
|
141
|
+
if (insertVec && embeddings[i]?.length > 0) {
|
|
142
|
+
deleteVec.run(e.name);
|
|
143
|
+
insertVec.run(e.name, serializeEmbedding(embeddings[i]));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
txn();
|
|
148
|
+
}
|
|
149
|
+
log.info(`Delta reindex: -${removed.length} +${added.length} tools`);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
log.error({ err: error }, "Delta reindex failed");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Hybrid search: vector + FTS5, same pattern as HybridSearch.
|
|
156
|
+
*/
|
|
157
|
+
async search(query, queryEmbedding, limit) {
|
|
158
|
+
const topK = limit ?? this.config.topK;
|
|
159
|
+
const vectorResults = this.vectorEnabled ? this.vectorSearch(queryEmbedding, topK * 3) : [];
|
|
160
|
+
const keywordResults = this.keywordSearch(query, topK * 3);
|
|
161
|
+
return this.mergeResults(vectorResults, keywordResults, topK);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if a tool name matches any always-include pattern.
|
|
165
|
+
*/
|
|
166
|
+
isAlwaysIncluded(toolName) {
|
|
167
|
+
for (const pattern of this.config.alwaysInclude) {
|
|
168
|
+
if (pattern.endsWith("*")) {
|
|
169
|
+
const prefix = pattern.slice(0, -1);
|
|
170
|
+
if (toolName.startsWith(prefix)) return true;
|
|
171
|
+
} else if (toolName === pattern) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
vectorSearch(embedding, limit) {
|
|
178
|
+
if (!this.vectorEnabled || embedding.length === 0) return [];
|
|
179
|
+
try {
|
|
180
|
+
const embeddingBuffer = serializeEmbedding(embedding);
|
|
181
|
+
const rows = this.db.prepare(
|
|
182
|
+
`
|
|
183
|
+
SELECT tv.name, ti.description, tv.distance
|
|
184
|
+
FROM (
|
|
185
|
+
SELECT name, distance
|
|
186
|
+
FROM tool_index_vec
|
|
187
|
+
WHERE embedding MATCH ? AND k = ?
|
|
188
|
+
) tv
|
|
189
|
+
JOIN tool_index ti ON ti.name = tv.name
|
|
190
|
+
`
|
|
191
|
+
).all(embeddingBuffer, limit);
|
|
192
|
+
return rows.map((row) => ({
|
|
193
|
+
name: row.name,
|
|
194
|
+
description: row.description,
|
|
195
|
+
score: 1 - row.distance,
|
|
196
|
+
vectorScore: 1 - row.distance
|
|
197
|
+
}));
|
|
198
|
+
} catch (error) {
|
|
199
|
+
log.error({ err: error }, "Vector search error");
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
keywordSearch(query, limit) {
|
|
204
|
+
const safeQuery = escapeFts5Query(query);
|
|
205
|
+
if (!safeQuery) return [];
|
|
206
|
+
try {
|
|
207
|
+
const rows = this.db.prepare(
|
|
208
|
+
`
|
|
209
|
+
SELECT ti.name, ti.description, rank as score
|
|
210
|
+
FROM tool_index_fts tf
|
|
211
|
+
JOIN tool_index ti ON ti.rowid = tf.rowid
|
|
212
|
+
WHERE tool_index_fts MATCH ?
|
|
213
|
+
ORDER BY rank
|
|
214
|
+
LIMIT ?
|
|
215
|
+
`
|
|
216
|
+
).all(safeQuery, limit);
|
|
217
|
+
return rows.map((row) => ({
|
|
218
|
+
name: row.name,
|
|
219
|
+
description: row.description,
|
|
220
|
+
score: bm25ToScore(row.score),
|
|
221
|
+
keywordScore: bm25ToScore(row.score)
|
|
222
|
+
}));
|
|
223
|
+
} catch (error) {
|
|
224
|
+
log.error({ err: error }, "FTS5 search error");
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
mergeResults(vectorResults, keywordResults, limit) {
|
|
229
|
+
const byName = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const r of vectorResults) {
|
|
231
|
+
byName.set(r.name, { ...r, vectorScore: r.score });
|
|
232
|
+
}
|
|
233
|
+
for (const r of keywordResults) {
|
|
234
|
+
const existing = byName.get(r.name);
|
|
235
|
+
if (existing) {
|
|
236
|
+
existing.keywordScore = r.keywordScore;
|
|
237
|
+
existing.score = TOOL_RAG_VECTOR_WEIGHT * (existing.vectorScore ?? 0) + TOOL_RAG_KEYWORD_WEIGHT * (r.keywordScore ?? 0);
|
|
238
|
+
} else {
|
|
239
|
+
byName.set(r.name, {
|
|
240
|
+
...r,
|
|
241
|
+
score: TOOL_RAG_KEYWORD_WEIGHT * (r.keywordScore ?? 0)
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return Array.from(byName.values()).filter((r) => r.score >= TOOL_RAG_MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
export {
|
|
249
|
+
ToolIndex
|
|
250
|
+
};
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
getTranscriptSize,
|
|
8
8
|
readTranscript,
|
|
9
9
|
transcriptExists
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OCLG5GKI.js";
|
|
11
11
|
import "./chunk-EYWNOHMJ.js";
|
|
12
|
+
import "./chunk-RCMD3U65.js";
|
|
12
13
|
import "./chunk-QGM4M3NI.js";
|
|
13
14
|
export {
|
|
14
15
|
appendToTranscript,
|