@wipcomputer/wip-ldm-os 0.4.73-alpha.12 → 0.4.73-alpha.14
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/dist/bridge/{chunk-HFSMW37U.js → chunk-RUQEH7GZ.js} +45 -25
- package/dist/bridge/cli.js +1 -1
- package/dist/bridge/core.js +1 -1
- package/dist/bridge/mcp-server.js +1 -1
- package/package.json +1 -1
- package/shared/rules/git-conventions.md +3 -3
- package/shared/rules/release-pipeline.md +1 -1
- package/shared/rules/security.md +1 -1
- package/shared/rules/workspace-boundaries.md +1 -1
- package/shared/rules/writing-style.md +1 -1
- package/src/bridge/core.ts +51 -26
- package/src/hosted-mcp/demo/agent.html +300 -0
- package/src/hosted-mcp/demo/agent.txt +84 -0
- package/src/hosted-mcp/demo/fallback.jpg +0 -0
- package/src/hosted-mcp/demo/footer.js +16 -0
- package/src/hosted-mcp/demo/index.html +1291 -0
- package/src/hosted-mcp/demo/privacy.html +230 -0
- package/src/hosted-mcp/demo/sprites.jpg +0 -0
- package/src/hosted-mcp/demo/sprites.png +0 -0
- package/src/hosted-mcp/demo/tos.html +205 -0
- package/src/hosted-mcp/package.json +2 -0
- package/src/hosted-mcp/server.mjs +1511 -18
- package/src/hosted-mcp/tools.mjs +16 -0
|
@@ -6,6 +6,27 @@ import { homedir } from "os";
|
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
var execAsync = promisify(exec);
|
|
9
|
+
var GATEWAY_HOST = "127.0.0.1";
|
|
10
|
+
var DEFAULT_GATEWAY_PORT = 18789;
|
|
11
|
+
var DEFAULT_INBOX_PORT = 18790;
|
|
12
|
+
var GATEWAY_TIMEOUT_MS = 15e3;
|
|
13
|
+
var OP_CLI_TIMEOUT_MS = 1e4;
|
|
14
|
+
var EMBEDDING_API_URL = "https://api.openai.com/v1/embeddings";
|
|
15
|
+
var DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
16
|
+
var DEFAULT_EMBEDDING_DIMS = 1536;
|
|
17
|
+
var VECTOR_SEARCH_ROW_LIMIT = 1e3;
|
|
18
|
+
var RECENCY_DECAY_RATE = 0.01;
|
|
19
|
+
var RECENCY_FLOOR = 0.5;
|
|
20
|
+
var FRESHNESS_FRESH_DAYS = 3;
|
|
21
|
+
var FRESHNESS_RECENT_DAYS = 7;
|
|
22
|
+
var FRESHNESS_AGING_DAYS = 14;
|
|
23
|
+
var DEFAULT_SEARCH_LIMIT = 5;
|
|
24
|
+
var WORKSPACE_MAX_DEPTH = 4;
|
|
25
|
+
var WORKSPACE_MAX_EXCERPTS = 5;
|
|
26
|
+
var WORKSPACE_MAX_RESULTS = 10;
|
|
27
|
+
var SKILL_EXEC_TIMEOUT_MS = 12e4;
|
|
28
|
+
var SKILL_EXEC_MAX_BUFFER = 10 * 1024 * 1024;
|
|
29
|
+
var MS_PER_DAY = 1e3 * 60 * 60 * 24;
|
|
9
30
|
var HOME = process.env.HOME || homedir();
|
|
10
31
|
var LDM_ROOT = process.env.LDM_ROOT || join(HOME, ".ldm");
|
|
11
32
|
function resolveConfig(overrides) {
|
|
@@ -14,9 +35,9 @@ function resolveConfig(overrides) {
|
|
|
14
35
|
openclawDir,
|
|
15
36
|
workspaceDir: overrides?.workspaceDir || join(openclawDir, "workspace"),
|
|
16
37
|
dbPath: overrides?.dbPath || join(openclawDir, "memory", "context-embeddings.sqlite"),
|
|
17
|
-
inboxPort: overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT ||
|
|
18
|
-
embeddingModel: overrides?.embeddingModel ||
|
|
19
|
-
embeddingDimensions: overrides?.embeddingDimensions ||
|
|
38
|
+
inboxPort: overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT || String(DEFAULT_INBOX_PORT), 10),
|
|
39
|
+
embeddingModel: overrides?.embeddingModel || DEFAULT_EMBEDDING_MODEL,
|
|
40
|
+
embeddingDimensions: overrides?.embeddingDimensions || DEFAULT_EMBEDDING_DIMS
|
|
20
41
|
};
|
|
21
42
|
}
|
|
22
43
|
function resolveConfigMulti(overrides) {
|
|
@@ -29,9 +50,9 @@ function resolveConfigMulti(overrides) {
|
|
|
29
50
|
openclawDir,
|
|
30
51
|
workspaceDir: raw.workspaceDir || overrides?.workspaceDir || join(openclawDir, "workspace"),
|
|
31
52
|
dbPath: raw.dbPath || overrides?.dbPath || join(openclawDir, "memory", "context-embeddings.sqlite"),
|
|
32
|
-
inboxPort: raw.inboxPort || overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT ||
|
|
33
|
-
embeddingModel: raw.embeddingModel || overrides?.embeddingModel ||
|
|
34
|
-
embeddingDimensions: raw.embeddingDimensions || overrides?.embeddingDimensions ||
|
|
53
|
+
inboxPort: raw.inboxPort || overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT || String(DEFAULT_INBOX_PORT), 10),
|
|
54
|
+
embeddingModel: raw.embeddingModel || overrides?.embeddingModel || DEFAULT_EMBEDDING_MODEL,
|
|
55
|
+
embeddingDimensions: raw.embeddingDimensions || overrides?.embeddingDimensions || DEFAULT_EMBEDDING_DIMS
|
|
35
56
|
};
|
|
36
57
|
} catch {
|
|
37
58
|
}
|
|
@@ -53,7 +74,7 @@ function resolveApiKey(openclawDir) {
|
|
|
53
74
|
`op read "op://Agent Secrets/OpenAI API/api key" 2>/dev/null`,
|
|
54
75
|
{
|
|
55
76
|
env: { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: saToken },
|
|
56
|
-
timeout:
|
|
77
|
+
timeout: OP_CLI_TIMEOUT_MS,
|
|
57
78
|
encoding: "utf-8"
|
|
58
79
|
}
|
|
59
80
|
).trim();
|
|
@@ -76,7 +97,7 @@ function resolveGatewayConfig(openclawDir) {
|
|
|
76
97
|
}
|
|
77
98
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
78
99
|
const token = config?.gateway?.auth?.token;
|
|
79
|
-
const port = config?.gateway?.port ||
|
|
100
|
+
const port = config?.gateway?.port || DEFAULT_GATEWAY_PORT;
|
|
80
101
|
if (!token) {
|
|
81
102
|
throw new Error("No gateway.auth.token found in openclaw.json");
|
|
82
103
|
}
|
|
@@ -263,10 +284,10 @@ async function sendMessage(openclawDir, message, options) {
|
|
|
263
284
|
const agentId = options?.agentId || "main";
|
|
264
285
|
const senderLabel = options?.senderLabel || "Claude Code";
|
|
265
286
|
const controller = new AbortController();
|
|
266
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
287
|
+
const timeoutId = setTimeout(() => controller.abort(), GATEWAY_TIMEOUT_MS);
|
|
267
288
|
try {
|
|
268
289
|
const response = await fetch(
|
|
269
|
-
`http
|
|
290
|
+
`http://${GATEWAY_HOST}:${port}/v1/chat/completions`,
|
|
270
291
|
{
|
|
271
292
|
method: "POST",
|
|
272
293
|
headers: {
|
|
@@ -308,8 +329,8 @@ async function sendMessage(openclawDir, message, options) {
|
|
|
308
329
|
throw err;
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
|
-
async function getQueryEmbedding(text, apiKey, model =
|
|
312
|
-
const response = await fetch(
|
|
332
|
+
async function getQueryEmbedding(text, apiKey, model = DEFAULT_EMBEDDING_MODEL, dimensions = DEFAULT_EMBEDDING_DIMS) {
|
|
333
|
+
const response = await fetch(EMBEDDING_API_URL, {
|
|
313
334
|
method: "POST",
|
|
314
335
|
headers: {
|
|
315
336
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -348,15 +369,15 @@ function cosineSimilarity(a, b) {
|
|
|
348
369
|
return denom === 0 ? 0 : dot / denom;
|
|
349
370
|
}
|
|
350
371
|
function recencyWeight(ageDays) {
|
|
351
|
-
return Math.max(
|
|
372
|
+
return Math.max(RECENCY_FLOOR, 1 - ageDays * RECENCY_DECAY_RATE);
|
|
352
373
|
}
|
|
353
374
|
function freshnessLabel(ageDays) {
|
|
354
|
-
if (ageDays <
|
|
355
|
-
if (ageDays <
|
|
356
|
-
if (ageDays <
|
|
375
|
+
if (ageDays < FRESHNESS_FRESH_DAYS) return "fresh";
|
|
376
|
+
if (ageDays < FRESHNESS_RECENT_DAYS) return "recent";
|
|
377
|
+
if (ageDays < FRESHNESS_AGING_DAYS) return "aging";
|
|
357
378
|
return "stale";
|
|
358
379
|
}
|
|
359
|
-
async function searchConversations(config, query, limit =
|
|
380
|
+
async function searchConversations(config, query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
360
381
|
const Database = (await import("better-sqlite3")).default;
|
|
361
382
|
if (!existsSync(config.dbPath)) {
|
|
362
383
|
throw new Error(`Database not found: ${config.dbPath}`);
|
|
@@ -377,12 +398,12 @@ async function searchConversations(config, query, limit = 5) {
|
|
|
377
398
|
FROM conversation_chunks
|
|
378
399
|
WHERE embedding IS NOT NULL
|
|
379
400
|
ORDER BY timestamp DESC
|
|
380
|
-
LIMIT
|
|
401
|
+
LIMIT ${VECTOR_SEARCH_ROW_LIMIT}`
|
|
381
402
|
).all();
|
|
382
403
|
const now = Date.now();
|
|
383
404
|
return rows.map((row) => {
|
|
384
405
|
const cosine = cosineSimilarity(queryEmbedding, blobToEmbedding(row.embedding));
|
|
385
|
-
const ageDays = (now - row.timestamp) /
|
|
406
|
+
const ageDays = (now - row.timestamp) / MS_PER_DAY;
|
|
386
407
|
const weight = recencyWeight(ageDays);
|
|
387
408
|
return {
|
|
388
409
|
text: row.chunk_text,
|
|
@@ -413,7 +434,7 @@ async function searchConversations(config, query, limit = 5) {
|
|
|
413
434
|
db.close();
|
|
414
435
|
}
|
|
415
436
|
}
|
|
416
|
-
function findMarkdownFiles(dir, maxDepth =
|
|
437
|
+
function findMarkdownFiles(dir, maxDepth = WORKSPACE_MAX_DEPTH, depth = 0) {
|
|
417
438
|
if (depth > maxDepth || !existsSync(dir)) return [];
|
|
418
439
|
const files = [];
|
|
419
440
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -443,7 +464,7 @@ function searchWorkspace(workspaceDir, query) {
|
|
|
443
464
|
if (score === 0) continue;
|
|
444
465
|
const lines = content.split("\n");
|
|
445
466
|
const excerpts = [];
|
|
446
|
-
for (let i = 0; i < lines.length && excerpts.length <
|
|
467
|
+
for (let i = 0; i < lines.length && excerpts.length < WORKSPACE_MAX_EXCERPTS; i++) {
|
|
447
468
|
const lineLower = lines[i].toLowerCase();
|
|
448
469
|
if (words.some((w) => lineLower.includes(w))) {
|
|
449
470
|
const start = Math.max(0, i - 1);
|
|
@@ -455,7 +476,7 @@ function searchWorkspace(workspaceDir, query) {
|
|
|
455
476
|
} catch {
|
|
456
477
|
}
|
|
457
478
|
}
|
|
458
|
-
return results.sort((a, b) => b.score - a.score).slice(0,
|
|
479
|
+
return results.sort((a, b) => b.score - a.score).slice(0, WORKSPACE_MAX_RESULTS);
|
|
459
480
|
}
|
|
460
481
|
function parseSkillFrontmatter(content) {
|
|
461
482
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -549,9 +570,8 @@ async function executeSkillScript(skillDir, scripts, scriptName, args) {
|
|
|
549
570
|
`${interpreter} "${scriptPath}" ${args}`,
|
|
550
571
|
{
|
|
551
572
|
env: { ...process.env },
|
|
552
|
-
timeout:
|
|
553
|
-
maxBuffer:
|
|
554
|
-
// 10MB
|
|
573
|
+
timeout: SKILL_EXEC_TIMEOUT_MS,
|
|
574
|
+
maxBuffer: SKILL_EXEC_MAX_BUFFER
|
|
555
575
|
}
|
|
556
576
|
);
|
|
557
577
|
return stdout || stderr || "(no output)";
|
package/dist/bridge/cli.js
CHANGED
package/dist/bridge/core.js
CHANGED
package/package.json
CHANGED
|
@@ -14,11 +14,11 @@ Always use a branch and PR.
|
|
|
14
14
|
|
|
15
15
|
## Co-authors on every commit
|
|
16
16
|
|
|
17
|
-
List all contributors. Read co-author lines from
|
|
17
|
+
List all contributors. Read co-author lines from `~/.ldm/config.json` coAuthors field.
|
|
18
18
|
|
|
19
19
|
## Branch prefixes
|
|
20
20
|
|
|
21
|
-
Each agent uses a prefix from
|
|
21
|
+
Each agent uses a prefix from `~/.ldm/config.json` agents section. Prevents collisions.
|
|
22
22
|
|
|
23
23
|
## Worktrees
|
|
24
24
|
|
|
@@ -30,4 +30,4 @@ For private/public repo pairs, all issues go on the public repo.
|
|
|
30
30
|
|
|
31
31
|
## On-demand reference
|
|
32
32
|
|
|
33
|
-
Before doing repo work, read `~/wipcomputerinc/
|
|
33
|
+
Before doing repo work, read `~/wipcomputerinc/library/documentation/how-worktrees-work.md` for the full worktree workflow with commands.
|
|
@@ -39,4 +39,4 @@ Installed tools are for execution. Repo clones are for development. Use the inst
|
|
|
39
39
|
|
|
40
40
|
## On-demand reference
|
|
41
41
|
|
|
42
|
-
Before releasing, read `~/wipcomputerinc/
|
|
42
|
+
Before releasing, read `~/wipcomputerinc/library/documentation/how-releases-work.md` for the full pipeline with commands.
|
package/shared/rules/security.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Secret management
|
|
4
4
|
|
|
5
|
-
Use your org's secret management tool (configured in
|
|
5
|
+
Use your org's secret management tool (configured in `~/.ldm/config.json`). Never hardcode API keys, tokens, or credentials.
|
|
6
6
|
|
|
7
7
|
## Security audit before installing anything
|
|
8
8
|
|
|
@@ -22,4 +22,4 @@ Installed tools are for execution. Repo clones are for development. Use the inst
|
|
|
22
22
|
|
|
23
23
|
## On-demand reference
|
|
24
24
|
|
|
25
|
-
For the full directory map, read `~/wipcomputerinc/
|
|
25
|
+
For the full directory map, read `~/wipcomputerinc/library/documentation/system-directories.md`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Writing Style
|
|
2
2
|
|
|
3
|
-
Read writing conventions from
|
|
3
|
+
Read writing conventions from `~/.ldm/config.json` writingStyle section.
|
|
4
4
|
|
|
5
5
|
**Full paths in documentation.** Never truncate paths. Always show the complete path so there's no ambiguity.
|
package/src/bridge/core.ts
CHANGED
|
@@ -10,6 +10,31 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
|
|
11
11
|
const execAsync = promisify(exec);
|
|
12
12
|
|
|
13
|
+
// ── Settings ─────────────────────────────────────────────────────────
|
|
14
|
+
// All tunable constants in one place. No magic numbers below this block.
|
|
15
|
+
|
|
16
|
+
const GATEWAY_HOST = "127.0.0.1";
|
|
17
|
+
const DEFAULT_GATEWAY_PORT = 18_789; // openclaw.json gateway.port fallback
|
|
18
|
+
const DEFAULT_INBOX_PORT = 18_790; // env LESA_BRIDGE_INBOX_PORT fallback
|
|
19
|
+
const GATEWAY_TIMEOUT_MS = 15_000; // max wait for gateway chat response
|
|
20
|
+
const OP_CLI_TIMEOUT_MS = 10_000; // max wait for 1Password CLI
|
|
21
|
+
const EMBEDDING_API_URL = "https://api.openai.com/v1/embeddings";
|
|
22
|
+
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
23
|
+
const DEFAULT_EMBEDDING_DIMS = 1_536;
|
|
24
|
+
const VECTOR_SEARCH_ROW_LIMIT = 1_000; // max rows scanned for cosine ranking
|
|
25
|
+
const RECENCY_DECAY_RATE = 0.01; // per-day decay multiplier
|
|
26
|
+
const RECENCY_FLOOR = 0.5; // minimum recency weight
|
|
27
|
+
const FRESHNESS_FRESH_DAYS = 3;
|
|
28
|
+
const FRESHNESS_RECENT_DAYS = 7;
|
|
29
|
+
const FRESHNESS_AGING_DAYS = 14;
|
|
30
|
+
const DEFAULT_SEARCH_LIMIT = 5; // default results for searchConversations
|
|
31
|
+
const WORKSPACE_MAX_DEPTH = 4; // findMarkdownFiles recursion limit
|
|
32
|
+
const WORKSPACE_MAX_EXCERPTS = 5; // max excerpts per file in search
|
|
33
|
+
const WORKSPACE_MAX_RESULTS = 10; // max files returned from workspace search
|
|
34
|
+
const SKILL_EXEC_TIMEOUT_MS = 120_000; // max wait for skill script execution
|
|
35
|
+
const SKILL_EXEC_MAX_BUFFER = 10 * 1024 * 1024; // 10 MB stdout/stderr cap
|
|
36
|
+
const MS_PER_DAY = 1_000 * 60 * 60 * 24;
|
|
37
|
+
|
|
13
38
|
// ── Constants ─────────────────────────────────────────────────────────
|
|
14
39
|
|
|
15
40
|
const HOME = process.env.HOME || homedir();
|
|
@@ -66,9 +91,9 @@ export function resolveConfig(overrides?: Partial<BridgeConfig>): BridgeConfig {
|
|
|
66
91
|
openclawDir,
|
|
67
92
|
workspaceDir: overrides?.workspaceDir || join(openclawDir, "workspace"),
|
|
68
93
|
dbPath: overrides?.dbPath || join(openclawDir, "memory", "context-embeddings.sqlite"),
|
|
69
|
-
inboxPort: overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT ||
|
|
70
|
-
embeddingModel: overrides?.embeddingModel ||
|
|
71
|
-
embeddingDimensions: overrides?.embeddingDimensions ||
|
|
94
|
+
inboxPort: overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT || String(DEFAULT_INBOX_PORT), 10),
|
|
95
|
+
embeddingModel: overrides?.embeddingModel || DEFAULT_EMBEDDING_MODEL,
|
|
96
|
+
embeddingDimensions: overrides?.embeddingDimensions || DEFAULT_EMBEDDING_DIMS,
|
|
72
97
|
};
|
|
73
98
|
}
|
|
74
99
|
|
|
@@ -88,9 +113,9 @@ export function resolveConfigMulti(overrides?: Partial<BridgeConfig>): BridgeCon
|
|
|
88
113
|
openclawDir,
|
|
89
114
|
workspaceDir: raw.workspaceDir || overrides?.workspaceDir || join(openclawDir, "workspace"),
|
|
90
115
|
dbPath: raw.dbPath || overrides?.dbPath || join(openclawDir, "memory", "context-embeddings.sqlite"),
|
|
91
|
-
inboxPort: raw.inboxPort || overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT ||
|
|
92
|
-
embeddingModel: raw.embeddingModel || overrides?.embeddingModel ||
|
|
93
|
-
embeddingDimensions: raw.embeddingDimensions || overrides?.embeddingDimensions ||
|
|
116
|
+
inboxPort: raw.inboxPort || overrides?.inboxPort || parseInt(process.env.LESA_BRIDGE_INBOX_PORT || String(DEFAULT_INBOX_PORT), 10),
|
|
117
|
+
embeddingModel: raw.embeddingModel || overrides?.embeddingModel || DEFAULT_EMBEDDING_MODEL,
|
|
118
|
+
embeddingDimensions: raw.embeddingDimensions || overrides?.embeddingDimensions || DEFAULT_EMBEDDING_DIMS,
|
|
94
119
|
};
|
|
95
120
|
} catch {
|
|
96
121
|
// LDM config unreadable, fall through to legacy
|
|
@@ -123,7 +148,7 @@ export function resolveApiKey(openclawDir: string): string | null {
|
|
|
123
148
|
`op read "op://Agent Secrets/OpenAI API/api key" 2>/dev/null`,
|
|
124
149
|
{
|
|
125
150
|
env: { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: saToken },
|
|
126
|
-
timeout:
|
|
151
|
+
timeout: OP_CLI_TIMEOUT_MS,
|
|
127
152
|
encoding: "utf-8",
|
|
128
153
|
}
|
|
129
154
|
).trim();
|
|
@@ -154,7 +179,7 @@ export function resolveGatewayConfig(openclawDir: string): GatewayConfig {
|
|
|
154
179
|
|
|
155
180
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
156
181
|
const token = config?.gateway?.auth?.token;
|
|
157
|
-
const port = config?.gateway?.port ||
|
|
182
|
+
const port = config?.gateway?.port || DEFAULT_GATEWAY_PORT;
|
|
158
183
|
|
|
159
184
|
if (!token) {
|
|
160
185
|
throw new Error("No gateway.auth.token found in openclaw.json");
|
|
@@ -460,11 +485,11 @@ export async function sendMessage(
|
|
|
460
485
|
// This ensures Parker sees CC's messages in the same stream as iMessage.
|
|
461
486
|
// The OpenClaw gateway treats user: "main" as "use the default session."
|
|
462
487
|
const controller = new AbortController();
|
|
463
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
488
|
+
const timeoutId = setTimeout(() => controller.abort(), GATEWAY_TIMEOUT_MS);
|
|
464
489
|
|
|
465
490
|
try {
|
|
466
491
|
const response = await fetch(
|
|
467
|
-
`http
|
|
492
|
+
`http://${GATEWAY_HOST}:${port}/v1/chat/completions`,
|
|
468
493
|
{
|
|
469
494
|
method: "POST",
|
|
470
495
|
headers: {
|
|
@@ -518,10 +543,10 @@ export async function sendMessage(
|
|
|
518
543
|
export async function getQueryEmbedding(
|
|
519
544
|
text: string,
|
|
520
545
|
apiKey: string,
|
|
521
|
-
model =
|
|
522
|
-
dimensions =
|
|
546
|
+
model = DEFAULT_EMBEDDING_MODEL,
|
|
547
|
+
dimensions = DEFAULT_EMBEDDING_DIMS
|
|
523
548
|
): Promise<number[]> {
|
|
524
|
-
const response = await fetch(
|
|
549
|
+
const response = await fetch(EMBEDDING_API_URL, {
|
|
525
550
|
method: "POST",
|
|
526
551
|
headers: {
|
|
527
552
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -567,15 +592,15 @@ export function cosineSimilarity(a: number[], b: number[]): number {
|
|
|
567
592
|
// ── Recency scoring ─────────────────────────────────────────────────
|
|
568
593
|
|
|
569
594
|
function recencyWeight(ageDays: number): number {
|
|
570
|
-
// Linear decay with floor
|
|
595
|
+
// Linear decay with floor. Old stuff never fully disappears
|
|
571
596
|
// but fresh context wins ties. ~50 days to hit the floor.
|
|
572
|
-
return Math.max(
|
|
597
|
+
return Math.max(RECENCY_FLOOR, 1.0 - ageDays * RECENCY_DECAY_RATE);
|
|
573
598
|
}
|
|
574
599
|
|
|
575
600
|
function freshnessLabel(ageDays: number): "fresh" | "recent" | "aging" | "stale" {
|
|
576
|
-
if (ageDays <
|
|
577
|
-
if (ageDays <
|
|
578
|
-
if (ageDays <
|
|
601
|
+
if (ageDays < FRESHNESS_FRESH_DAYS) return "fresh";
|
|
602
|
+
if (ageDays < FRESHNESS_RECENT_DAYS) return "recent";
|
|
603
|
+
if (ageDays < FRESHNESS_AGING_DAYS) return "aging";
|
|
579
604
|
return "stale";
|
|
580
605
|
}
|
|
581
606
|
|
|
@@ -584,7 +609,7 @@ function freshnessLabel(ageDays: number): "fresh" | "recent" | "aging" | "stale"
|
|
|
584
609
|
export async function searchConversations(
|
|
585
610
|
config: BridgeConfig,
|
|
586
611
|
query: string,
|
|
587
|
-
limit =
|
|
612
|
+
limit = DEFAULT_SEARCH_LIMIT
|
|
588
613
|
): Promise<ConversationResult[]> {
|
|
589
614
|
// Lazy import to avoid requiring better-sqlite3 if not needed
|
|
590
615
|
const Database = (await import("better-sqlite3")).default;
|
|
@@ -611,7 +636,7 @@ export async function searchConversations(
|
|
|
611
636
|
FROM conversation_chunks
|
|
612
637
|
WHERE embedding IS NOT NULL
|
|
613
638
|
ORDER BY timestamp DESC
|
|
614
|
-
LIMIT
|
|
639
|
+
LIMIT ${VECTOR_SEARCH_ROW_LIMIT}`
|
|
615
640
|
)
|
|
616
641
|
.all() as Array<{
|
|
617
642
|
chunk_text: string;
|
|
@@ -625,7 +650,7 @@ export async function searchConversations(
|
|
|
625
650
|
return rows
|
|
626
651
|
.map((row) => {
|
|
627
652
|
const cosine = cosineSimilarity(queryEmbedding, blobToEmbedding(row.embedding));
|
|
628
|
-
const ageDays = (now - row.timestamp) /
|
|
653
|
+
const ageDays = (now - row.timestamp) / MS_PER_DAY;
|
|
629
654
|
const weight = recencyWeight(ageDays);
|
|
630
655
|
return {
|
|
631
656
|
text: row.chunk_text,
|
|
@@ -670,7 +695,7 @@ export async function searchConversations(
|
|
|
670
695
|
|
|
671
696
|
// ── Workspace search ─────────────────────────────────────────────────
|
|
672
697
|
|
|
673
|
-
export function findMarkdownFiles(dir: string, maxDepth =
|
|
698
|
+
export function findMarkdownFiles(dir: string, maxDepth = WORKSPACE_MAX_DEPTH, depth = 0): string[] {
|
|
674
699
|
if (depth > maxDepth || !existsSync(dir)) return [];
|
|
675
700
|
|
|
676
701
|
const files: string[] = [];
|
|
@@ -705,7 +730,7 @@ export function searchWorkspace(workspaceDir: string, query: string): WorkspaceS
|
|
|
705
730
|
|
|
706
731
|
const lines = content.split("\n");
|
|
707
732
|
const excerpts: string[] = [];
|
|
708
|
-
for (let i = 0; i < lines.length && excerpts.length <
|
|
733
|
+
for (let i = 0; i < lines.length && excerpts.length < WORKSPACE_MAX_EXCERPTS; i++) {
|
|
709
734
|
const lineLower = lines[i].toLowerCase();
|
|
710
735
|
if (words.some((w) => lineLower.includes(w))) {
|
|
711
736
|
const start = Math.max(0, i - 1);
|
|
@@ -720,7 +745,7 @@ export function searchWorkspace(workspaceDir: string, query: string): WorkspaceS
|
|
|
720
745
|
}
|
|
721
746
|
}
|
|
722
747
|
|
|
723
|
-
return results.sort((a, b) => b.score - a.score).slice(0,
|
|
748
|
+
return results.sort((a, b) => b.score - a.score).slice(0, WORKSPACE_MAX_RESULTS);
|
|
724
749
|
}
|
|
725
750
|
|
|
726
751
|
// ── Read workspace file ──────────────────────────────────────────────
|
|
@@ -876,8 +901,8 @@ export async function executeSkillScript(
|
|
|
876
901
|
`${interpreter} "${scriptPath}" ${args}`,
|
|
877
902
|
{
|
|
878
903
|
env: { ...process.env },
|
|
879
|
-
timeout:
|
|
880
|
-
maxBuffer:
|
|
904
|
+
timeout: SKILL_EXEC_TIMEOUT_MS,
|
|
905
|
+
maxBuffer: SKILL_EXEC_MAX_BUFFER,
|
|
881
906
|
}
|
|
882
907
|
);
|
|
883
908
|
return stdout || stderr || "(no output)";
|